Compare commits
53 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 |
585
README.md
@ -1,10 +1,11 @@
|
||||
# ESPUI
|
||||
|
||||

|
||||

|
||||
|
||||
ESPUI is a simple library to make a web user interface for your projects using
|
||||
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,
|
||||
control, and update elements on your GUI through multiple devices like phones
|
||||
|
||||
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
|
||||
@ -14,35 +15,62 @@ 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 fine on any kind of **ESP8266** and **ESP32** (NodeMCU Boards, usw)
|
||||
The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker, etc.).
|
||||
|
||||
## Changelog for 2.0:
|
||||
- [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)
|
||||
|
||||
- ArduinoJSON 6.10.0 Support
|
||||
- split pad into pad and padWithCenter
|
||||
- Cleaned Order or parameters on switch
|
||||
- cleaned Order of parameters on pad
|
||||
- Changes all numbers to actually be numbers (slider value, number value, min and max)
|
||||
|
||||
### Added features
|
||||
### Contributed features
|
||||
|
||||
- Tabs by @eringerli #45
|
||||
- Tabs by @eringerli
|
||||
- Generic API by @eringerli
|
||||
- Min Max on slider by @eringerli
|
||||
- OptionList by @eringerli
|
||||
- Public Access to ESPAsyncServer
|
||||
- Graph Widget (Persist save graph in local storage #10)
|
||||
- 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
|
||||
|
||||
## Further Roadmap
|
||||
## Roadmap
|
||||
|
||||
- Slider css issues
|
||||
- implement Gauge
|
||||
- File upload ?
|
||||
- Fully implement graphs
|
||||
- Expand number input features (floats etc.)
|
||||
- Support for enabling and disabling controls
|
||||
|
||||
## 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)
|
||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) (Last tested with
|
||||
@ -56,37 +84,25 @@ This library is dependent on the following libraries to function properly.
|
||||
|
||||
Make sure all the dependencies are installed, then install like so:
|
||||
|
||||
#### Using PlattformIO (_recommended_)
|
||||
#### Using PlatformIO (_recommended_)
|
||||
|
||||
Just include this library as a dependency on lib_deps like so:
|
||||
Just include this library as a dependency in `lib_deps` like so:
|
||||
|
||||
```
|
||||
lib_deps =
|
||||
ESPUI
|
||||
ESPAsyncWebserver
|
||||
ESPAsyncTCP # or AsyncTCP on ESP32
|
||||
ESP Async WebServer
|
||||
ESPAsyncTCP # (or AsyncTCP on ESP32)
|
||||
LittleFS_esp32 # (ESP32 only)
|
||||
```
|
||||
|
||||
#### Directly Through Arduino IDE (_recommended_)
|
||||
#### Using the Arduino IDE (_recommended_)
|
||||
|
||||
You can find this Library in the Arduino IDE library manager Go to Sketch >
|
||||
Include Library > Library Manager > Search for "ESPUI" > Install
|
||||
You can find this Library in the Arduino IDE library manager. Go to
|
||||
`Sketch > Include Library > Library Manager` search for `ESPUI` and install.
|
||||
|
||||
#### Manual Install Arduino IDE
|
||||
|
||||
_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}
|
||||
|
||||
_For Linux:_ Download the
|
||||
[Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the
|
||||
.zip in Sketchbook/Libraries/{Place "ESPUI" folder Here}
|
||||
|
||||
_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}
|
||||
|
||||
Go to Sketch>Include Library>Add .zip Library> Select the Downloaded .zip File.
|
||||
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).
|
||||
|
||||
## Getting started
|
||||
|
||||
@ -118,36 +134,23 @@ more program memory to work with.
|
||||
- Button
|
||||
- Switch
|
||||
- Control pad
|
||||
- Control pad with center button
|
||||
- Slider
|
||||
- Text Input
|
||||
- Date, Time, Colour and Password Input
|
||||
- Numberinput
|
||||
- Graph
|
||||
- Option select
|
||||
- Separator
|
||||
- Time
|
||||
- Graph (partial implementation)
|
||||
|
||||
Checkout the example for the usage or see the detailed info below
|
||||
|
||||
## Available colors:
|
||||
|
||||
- Turquoise
|
||||
- Emerald
|
||||
- Peterriver
|
||||
- Wetasphalt
|
||||
- Sunflower
|
||||
- Carrot
|
||||
- Alizarin
|
||||
- Dark
|
||||
- None
|
||||
|
||||
(Use like `ControlColor::Sunflower`)
|
||||
|
||||
## Documentation
|
||||
|
||||
The heart of ESPUI is
|
||||
[ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). 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 _ESP_ and the client browser works using web
|
||||
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.
|
||||
|
||||
@ -160,60 +163,58 @@ 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:
|
||||
statement.
|
||||
|
||||
|
||||
#### Button
|
||||
|
||||

|
||||

|
||||
|
||||
Buttons have a name and a callback value. They have one event for press (`B_DOWN`) and one
|
||||
for release (`B_UP`).
|
||||
Buttons have a name and a callback value. Their text can be changed at runtime using `ESPUI.updateButton()`.
|
||||
|
||||
- B_DOWN
|
||||
- B_UP
|
||||
Events:
|
||||
- `B_DOWN` - Fired when button is pressed.
|
||||
- `B_UP` - Fired when button is released.
|
||||
|
||||
#### 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 when turning on (`S_ACTIVE`)
|
||||
and one when turning off (`S_INACTIVE`).
|
||||
their value (either by pressing them, or programmatically using `ESPUI.updateSwitcher()`) they change visibly
|
||||
on all tablets or computers that currently display the interface.
|
||||
|
||||
- S_ACTIVE
|
||||
- S_INACTIVE
|
||||
Events:
|
||||
- `S_ACTIVE` - Fired when turning on.
|
||||
- `S_INACTIVE` - Fired when turning off.
|
||||
|
||||
#### 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
|
||||
- `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 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.
|
||||
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.
|
||||
@ -230,107 +231,207 @@ This requires that the client has access to the image in question, either from t
|
||||
|
||||
#### Slider
|
||||
|
||||

|
||||

|
||||
|
||||
The Slider can be used to slide through a value from 1 to 100. Slides provide
|
||||
realtime data, are touch compatible and can be used to for example control a
|
||||
Servo. The current value is shown while the slider is dragged in a little bubble
|
||||
over the handle. In the Callback the slider does not return an int but a String.
|
||||
Use the .toInt function to convert the value, see the **gui** example to check how it works.
|
||||
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()`.
|
||||
|
||||
A slider usually only sends a new value when it is released to save the esps from being spammed with values. This behaviour can be cahnged globally using a property of the ESPUI object before `begin()`:
|
||||
A slider usually only sends a new value when it is released to save network bandwidth.
|
||||
This behaviour can be changed globally by setting `sliderContinuous` before `begin()`:
|
||||
|
||||
```
|
||||
ESPUI.sliderContinuous = true;
|
||||
ESPUI.begin("ESPUI Control");
|
||||
ESPUI.sliderContinuous = true;
|
||||
ESPUI.begin("ESPUI Control");
|
||||
```
|
||||
|
||||
Events:
|
||||
- `SL_VALUE` - Fired when a slider value changes.
|
||||
|
||||
#### Number Input
|
||||
|
||||

|
||||

|
||||
|
||||
The numberinput can be used to directly input numbers to your program. 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:
|
||||
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 textinput works very similar like the number input but with a string. You
|
||||
can enter a String into it and when you are done with your change it is sent to
|
||||
the ESP.
|
||||
|
||||
#### Graph
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
#### Option select
|
||||
|
||||

|
||||

|
||||
|
||||
The option select works by first creating a select widget like so
|
||||
|
||||
`uint16_t select1 = ESPUI.addControl( ControlType::Select, "Select:", "", ControlColor::Alizarin, tab1, &selectExample );`
|
||||
|
||||
And then adding Options to it like seperate widgets, specifying the select as the parent:
|
||||
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.
|
||||
|
||||
```
|
||||
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 );
|
||||
text = ESPUI.text("Label", callback, ControlColor::Dark, "Initial value");
|
||||
ESPUI.addControl(ControlType::Max, "", "32", ControlColor::None, text);
|
||||
```
|
||||
|
||||
Check the **tabbedGui** example for a working demo
|
||||
Text inputs can be updated from code using `ESP.updateText()`.
|
||||
|
||||
### Using Tabs
|
||||
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.
|
||||
|
||||
Tabs can be used to organize your widgets in pages. Check the tabbedGui example.
|
||||
Tabs can be created using the generic functions like so:
|
||||
`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 parrent (widgets not added to a tab will be shown above the tab selctor)
|
||||
#### Date, Time, Colour and Password Input
|
||||
|
||||
`ESPUI.addControl( ControlType::Text, "Text Title:", "a Text Field", ControlColor::Alizarin, tab1, &textCall );`
|
||||

|
||||
|
||||
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 you can use `ESPUI.begin("Some Title");`
|
||||
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 AccesPoint **before** (See
|
||||
gui.ino example). The web interface can then be used from multiple devices at once and
|
||||
also shows an connection status in the top bar.
|
||||
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.
|
||||
|
||||
### Advanced: Generic creation and updates of control widgets
|
||||
|
||||
There are 2 generic functions to create and update controls, to see them in action check the **gui-generic-api** example.
|
||||
|
||||
To create a generic control use:
|
||||
`uint16_t switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample);`
|
||||
### Tabs
|
||||
|
||||
Then its value can be updated by doing:
|
||||

|
||||
|
||||
`ESPUI.updateControlValue(status, "Start");`
|
||||
|
||||
You can also update other parameters of the control like its color using:
|
||||
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.getControl(switchOne)->color = ControlColor::Carrot;
|
||||
ESPUI.updateControl(switchOne);
|
||||
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
|
||||
@ -344,24 +445,39 @@ Loglevels are:
|
||||
|
||||
VerboseJSON outputs the most debug information.
|
||||
|
||||
### Advanced properties
|
||||
### Colours
|
||||
|
||||
If you have many different widgets it might be necessary to adjust the JSON Buffers used internally in ESPUI before .begin() :
|
||||
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.jsonUpdateDocumentSize = 2000; // This is the default, and this value is not affected by the amount of widgets
|
||||
ESPUI.jsonInitialDocumentSize = 8000; // This is the default, adjust when you have too many widgets or options
|
||||
ESPUI.begin("ESPUI Control");
|
||||
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)
|
||||
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.
|
||||
@ -375,21 +491,124 @@ be used to refect a warning state by changing the color of a button, or for simi
|
||||
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);
|
||||
char stylecol[30];
|
||||
sprintf(stylecol, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
|
||||
ESPUI.setPanelStyle(switch1, stylecol);
|
||||
```
|
||||
|
||||
You can get quite creative with this.
|
||||
|
||||

|
||||
|
||||
Note: The images in this example are formed by setting a Label to contain an `<img>` tag:
|
||||
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.addControl(ControlType::Label, "Label", "<img src='path/to/image'>", ControlColor::Peterriver);
|
||||
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
|
||||
|
||||
@ -410,6 +629,34 @@ 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
|
||||
|
||||
Liked this Library? You can **support** me by sending me a :coffee:
|
||||
|
@ -13,22 +13,44 @@
|
||||
|
||||
.card {
|
||||
min-height: 100px;
|
||||
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;
|
||||
margin-bottom: 40px;
|
||||
min-width: 500px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1205px) {
|
||||
.wide.card {
|
||||
min-width: 1075px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1790px) {
|
||||
.wide.card {
|
||||
min-width: 1650px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 630px) {
|
||||
.card {
|
||||
min-width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
.sectionbreak.columns {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.sectionbreak.columns hr {
|
||||
border: none;
|
||||
height: 2px;
|
||||
background-color: #666
|
||||
}
|
||||
|
||||
.card-slider {}
|
||||
|
||||
.turquoise {
|
||||
@ -137,7 +159,7 @@
|
||||
|
||||
.column,
|
||||
.columns {
|
||||
margin-right: 2%;
|
||||
margin-right: 35px;
|
||||
}
|
||||
|
||||
.column:first-child,
|
||||
@ -400,7 +422,7 @@ button {
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
button:active {
|
||||
button:enabled:active {
|
||||
background-color: #666666;
|
||||
transform: translateX(4px) translateY(4px);
|
||||
}
|
||||
@ -423,6 +445,8 @@ button:active {
|
||||
button,
|
||||
.button {
|
||||
margin-bottom: 1rem;
|
||||
margin-left: 0.3rem;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
/* Utilities
|
||||
@ -563,6 +587,13 @@ hr {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.control:not(.disabled) a.confirm:active {
|
||||
background-color:#777
|
||||
}
|
||||
.control:not(.disabled) li:active {
|
||||
background-color:#777
|
||||
}
|
||||
|
||||
/* Switch
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
|
||||
@ -576,7 +607,8 @@ hr {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
height: 26px;
|
||||
margin-bottom: 12px;
|
||||
margin-left: 0.3rem;
|
||||
margin-right: 0.3rem;
|
||||
position: relative;
|
||||
width: 60px;
|
||||
-webkit-transition: background-color 0.2s ease-in-out;
|
||||
@ -933,6 +965,7 @@ input[id^="num"] {
|
||||
|
||||
body div>ul.navigation {
|
||||
margin: 0;
|
||||
margin-bottom: 30px;
|
||||
padding: 0;
|
||||
border-bottom: 3px solid #666;
|
||||
overflow: hidden;
|
||||
@ -1102,3 +1135,66 @@ svg {
|
||||
text {
|
||||
text-anchor: end;
|
||||
}
|
||||
|
||||
|
||||
/* Styles to implement vertical orientations */
|
||||
|
||||
.vert-switcher {
|
||||
transform: rotate(270deg);
|
||||
margin-top: 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.vert-slider {
|
||||
width: 150px;
|
||||
transform: rotate(270deg);
|
||||
display: inline-block;
|
||||
margin: 50px -42px 70px -42px;
|
||||
}
|
||||
|
||||
.vert-slider span {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
|
||||
/* Styles to implement disabled controls */
|
||||
|
||||
button:disabled {
|
||||
color: #333;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
color: #333;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #333;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.range-slider__range:disabled {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.range-slider__range:disabled::-webkit-slider-thumb {
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
.range-slider__range:disabled::-moz-range-thumb {
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
.switch.disabled .in::before {
|
||||
background:#bbb;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.switch.disabled .in::after {
|
||||
background:#bbb;
|
||||
}
|
||||
|
||||
.switch.checked.disabled {
|
||||
background: #b1d092;
|
||||
}
|
||||
|
2
data/css/style.min.css
vendored
795
data/js/controls.js
vendored
@ -52,7 +52,13 @@ const UPDATE_STEP = 116;
|
||||
const UI_GAUGE = 17;
|
||||
const UPDATE_GAUGE = 117;
|
||||
const UI_ACCEL = 18;
|
||||
const UPTDATE_ACCEL = 117;
|
||||
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;
|
||||
@ -99,6 +105,7 @@ function colorClass(colorId) {
|
||||
case C_ALIZARIN:
|
||||
return "alizarin";
|
||||
|
||||
case C_DARK:
|
||||
case C_NONE:
|
||||
return "dark";
|
||||
default:
|
||||
@ -205,7 +212,7 @@ function start() {
|
||||
window.location.port != 443
|
||||
) {
|
||||
websock = new WebSocket(
|
||||
"ws://" + window.location.hostname + ":" + window.location.port + "/ws"
|
||||
"ws://" + window.location.hostname + ":" + window.location.port + "/ws"
|
||||
);
|
||||
} else {
|
||||
websock = new WebSocket("ws://" + window.location.hostname + "/ws");
|
||||
@ -233,9 +240,6 @@ function start() {
|
||||
var e = document.body;
|
||||
var center = "";
|
||||
|
||||
panelStyle = data.hasOwnProperty('panelStyle') ? " style='" + data.panelStyle + "'" : "";
|
||||
elementStyle = data.hasOwnProperty('elementStyle') ? " style='" + data.elementStyle + "'" : "";
|
||||
|
||||
switch (data.type) {
|
||||
case UI_INITIAL_GUI:
|
||||
// Clear current elements
|
||||
@ -252,6 +256,13 @@ function start() {
|
||||
};
|
||||
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:
|
||||
@ -261,6 +272,11 @@ function start() {
|
||||
};
|
||||
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:
|
||||
@ -272,366 +288,130 @@ function start() {
|
||||
$("#mainHeader").html(data.label);
|
||||
break;
|
||||
|
||||
/*
|
||||
Most elements have the same behaviour when added.
|
||||
*/
|
||||
case UI_LABEL:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<span id='l" +
|
||||
data.id +
|
||||
"' " + elementStyle + " class='label label-wrap'>" +
|
||||
data.value +
|
||||
"</span>" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
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:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='one columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<button id='btn" +
|
||||
data.id +
|
||||
"' " + elementStyle + " " +
|
||||
"onmousedown='buttonclick(" +
|
||||
data.id +
|
||||
", true)' " +
|
||||
"onmouseup='buttonclick(" +
|
||||
data.id +
|
||||
", false)'>" +
|
||||
data.value +
|
||||
"</button></div>"
|
||||
);
|
||||
$("#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:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
if (data.visible) {
|
||||
addToHTML(data);
|
||||
switcher(data.id, data.value);
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='one columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<label id='sl" +
|
||||
data.id +
|
||||
"' " + elementStyle + " class='switch " +
|
||||
(data.value == "1" ? "checked" : "") +
|
||||
"'>" +
|
||||
"<div class='in'><input type='checkbox' id='s" +
|
||||
data.id +
|
||||
"' onClick='switcher(" +
|
||||
data.id +
|
||||
",null)' " +
|
||||
(data.value == "1" ? "checked" : "") +
|
||||
"/></div>" +
|
||||
"</label>" +
|
||||
"</div>"
|
||||
);
|
||||
switcher(data.id, data.value);
|
||||
}
|
||||
break;
|
||||
|
||||
case UI_CPAD:
|
||||
case UI_PAD:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<nav class='control'>" +
|
||||
"<ul>" +
|
||||
"<li><a onmousedown='padclick(UP, " +
|
||||
data.id +
|
||||
", true)' onmouseup='padclick(UP, " +
|
||||
data.id +
|
||||
", false)' id='pf" +
|
||||
data.id +
|
||||
"'>▲</a></li>" +
|
||||
"<li><a onmousedown='padclick(RIGHT, " +
|
||||
data.id +
|
||||
", true)' onmouseup='padclick(RIGHT, " +
|
||||
data.id +
|
||||
", false)' id='pr" +
|
||||
data.id +
|
||||
"'>▲</a></li>" +
|
||||
"<li><a onmousedown='padclick(LEFT, " +
|
||||
data.id +
|
||||
", true)' onmouseup='padclick(LEFT, " +
|
||||
data.id +
|
||||
", false)' id='pl" +
|
||||
data.id +
|
||||
"'>▲</a></li>" +
|
||||
"<li><a onmousedown='padclick(DOWN, " +
|
||||
data.id +
|
||||
", true)' onmouseup='padclick(DOWN, " +
|
||||
data.id +
|
||||
", false)' id='pb" +
|
||||
data.id +
|
||||
"'>▲</a></li>" +
|
||||
"</ul>" +
|
||||
(data.type == UI_CPAD
|
||||
? "<a class='confirm' onmousedown='padclick(CENTER," +
|
||||
data.id +
|
||||
", true)' onmouseup='padclick(CENTER, " +
|
||||
data.id +
|
||||
", false)' id='pc" +
|
||||
data.id +
|
||||
"'>OK</a>"
|
||||
: "") +
|
||||
"</nav>" +
|
||||
"</div>"
|
||||
);
|
||||
|
||||
$("#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);
|
||||
},
|
||||
});
|
||||
}
|
||||
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;
|
||||
|
||||
//https://codepen.io/seanstopnik/pen/CeLqA
|
||||
case UI_SLIDER:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
//https://codepen.io/seanstopnik/pen/CeLqA
|
||||
if (data.visible) {
|
||||
addToHTML(data);
|
||||
rangeSlider(!sliderContinuous);
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter card-slider " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<div class='range-slider'>" +
|
||||
"<input id='sl" +
|
||||
data.id +
|
||||
"' type='range' min='0' max='100' value='" +
|
||||
data.value +
|
||||
"' " + elementStyle + " class='range-slider__range'>" +
|
||||
"<span class='range-slider__value'>" +
|
||||
data.value +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
rangeSlider(!sliderContinuous);
|
||||
}
|
||||
break;
|
||||
|
||||
case UI_NUMBER:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<input style='color:black;' " + elementStyle + " id='num" +
|
||||
data.id +
|
||||
"' type='number' value='" +
|
||||
data.value +
|
||||
"' onchange='numberchange(" +
|
||||
data.id +
|
||||
")' />" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case UI_TEXT_INPUT:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<input style='color:black;' " + elementStyle + " id='text" +
|
||||
data.id +
|
||||
"' value='" +
|
||||
data.value +
|
||||
"' onchange='textchange(" +
|
||||
data.id +
|
||||
")' />" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
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>");
|
||||
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_SELECT:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<select style='color:black;' " + elementStyle + " id='select" +
|
||||
data.id +
|
||||
"' onchange='selectchange(" +
|
||||
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:
|
||||
@ -653,18 +433,27 @@ function start() {
|
||||
|
||||
case UI_MIN:
|
||||
if (data.parentControl) {
|
||||
var parent = $("#id" + data.parentControl + " input");
|
||||
if (parent.size()) {
|
||||
parent.attr("min", data.value);
|
||||
//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) {
|
||||
var parent = $("#id" + data.parentControl + " input");
|
||||
if (parent.size()) {
|
||||
parent.attr("max", data.value);
|
||||
//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;
|
||||
@ -677,35 +466,13 @@ function start() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UI_GRAPH:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
if (data.visible) {
|
||||
addToHTML(data);
|
||||
graphData[data.id] = restoreGraphData(data.id);
|
||||
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"<figure id='graph" +
|
||||
data.id +
|
||||
"'>" +
|
||||
"<figcaption>" +
|
||||
data.label +
|
||||
"</figcaption>" +
|
||||
"</figure>" +
|
||||
"</div>"
|
||||
);
|
||||
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);
|
||||
@ -718,68 +485,19 @@ function start() {
|
||||
saveGraphData();
|
||||
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||
break;
|
||||
case UI_GAUGE:
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"WILL BE A GAUGE <input style='color:black;' id='gauge" +
|
||||
data.id +
|
||||
"' type='number' value='" +
|
||||
data.value +
|
||||
"' onchange='numberchange(" +
|
||||
data.id +
|
||||
")' />" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case UI_ACCEL:
|
||||
if (hasAccel) break;
|
||||
var parent;
|
||||
if (data.parentControl) {
|
||||
parent = $("#tab" + data.parentControl);
|
||||
} else {
|
||||
parent = $("#row");
|
||||
}
|
||||
hasAccel = true;
|
||||
if (data.visible) {
|
||||
parent.append(
|
||||
"<div id='id" +
|
||||
data.id +
|
||||
"' " + panelStyle + " class='two columns card tcenter " +
|
||||
colorClass(data.color) +
|
||||
"'>" +
|
||||
"<h5>" +
|
||||
data.label +
|
||||
"</h5><hr/>" +
|
||||
"ACCEL // Not implemented fully!<div class='accelerometer' id='accel" +
|
||||
data.id +
|
||||
"' ><div class='ball" +
|
||||
data.id +
|
||||
"'></div><pre class='accelerometeroutput" +
|
||||
data.id +
|
||||
"'></pre>" +
|
||||
"</div>"
|
||||
);
|
||||
|
||||
requestOrientationPermission();
|
||||
}
|
||||
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')) {
|
||||
@ -795,6 +513,7 @@ function start() {
|
||||
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);
|
||||
@ -813,6 +532,9 @@ function start() {
|
||||
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:
|
||||
@ -823,6 +545,13 @@ function start() {
|
||||
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;
|
||||
@ -835,18 +564,36 @@ function start() {
|
||||
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"
|
||||
@ -858,6 +605,8 @@ function start() {
|
||||
);
|
||||
element.addClass(colorClass(data.color));
|
||||
}
|
||||
|
||||
processEnabled(data);
|
||||
}
|
||||
|
||||
$(".range-slider__range").each(function(){
|
||||
@ -872,6 +621,10 @@ function start() {
|
||||
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) {
|
||||
@ -900,6 +653,9 @@ function buttonclick(number, isdown) {
|
||||
}
|
||||
|
||||
function padclick(type, number, isdown) {
|
||||
if($("#id" + number + " nav").hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case CENTER:
|
||||
if (isdown) websock.send("pcdown:" + number);
|
||||
@ -926,7 +682,7 @@ function padclick(type, number, isdown) {
|
||||
|
||||
function switcher(number, state) {
|
||||
if (state == null) {
|
||||
if ($("#s" + number).is(":checked")) {
|
||||
if (!$("#sl" + number).hasClass("checked")) {
|
||||
websock.send("sactive:" + number);
|
||||
$("#sl" + number).addClass("checked");
|
||||
} else {
|
||||
@ -964,3 +720,174 @@ var rangeSlider = function (isDiscrete) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
310
data/js/controls.min.js
vendored
@ -1,4 +1,4 @@
|
||||
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 UPTDATE_ACCEL=117;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_NONE:return"dark";default:return"";}}
|
||||
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];}
|
||||
@ -7,185 +7,15 @@ function restart(){$(document).add("*").off();$("#row").html("");websock.close()
|
||||
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="";panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"'":"";elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"'":"";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);});break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<span id='l"+
|
||||
data.id+
|
||||
"' "+elementStyle+" class='label label-wrap'>"+
|
||||
data.value+
|
||||
"</span>"+
|
||||
"</div>");}
|
||||
break;case UI_BUTTON:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='one columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<button id='btn"+
|
||||
data.id+
|
||||
"' "+elementStyle+" "+
|
||||
"onmousedown='buttonclick("+
|
||||
data.id+
|
||||
", true)' "+
|
||||
"onmouseup='buttonclick("+
|
||||
data.id+
|
||||
", false)'>"+
|
||||
data.value+
|
||||
"</button></div>");$("#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:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='one columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<label id='sl"+
|
||||
data.id+
|
||||
"' "+elementStyle+" class='switch "+
|
||||
(data.value=="1"?"checked":"")+
|
||||
"'>"+
|
||||
"<div class='in'><input type='checkbox' id='s"+
|
||||
data.id+
|
||||
"' onClick='switcher("+
|
||||
data.id+
|
||||
",null)' "+
|
||||
(data.value=="1"?"checked":"")+
|
||||
"/></div>"+
|
||||
"</label>"+
|
||||
"</div>");switcher(data.id,data.value);}
|
||||
break;case UI_CPAD:case UI_PAD:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<nav class='control'>"+
|
||||
"<ul>"+
|
||||
"<li><a onmousedown='padclick(UP, "+
|
||||
data.id+
|
||||
", true)' onmouseup='padclick(UP, "+
|
||||
data.id+
|
||||
", false)' id='pf"+
|
||||
data.id+
|
||||
"'>▲</a></li>"+
|
||||
"<li><a onmousedown='padclick(RIGHT, "+
|
||||
data.id+
|
||||
", true)' onmouseup='padclick(RIGHT, "+
|
||||
data.id+
|
||||
", false)' id='pr"+
|
||||
data.id+
|
||||
"'>▲</a></li>"+
|
||||
"<li><a onmousedown='padclick(LEFT, "+
|
||||
data.id+
|
||||
", true)' onmouseup='padclick(LEFT, "+
|
||||
data.id+
|
||||
", false)' id='pl"+
|
||||
data.id+
|
||||
"'>▲</a></li>"+
|
||||
"<li><a onmousedown='padclick(DOWN, "+
|
||||
data.id+
|
||||
", true)' onmouseup='padclick(DOWN, "+
|
||||
data.id+
|
||||
", false)' id='pb"+
|
||||
data.id+
|
||||
"'>▲</a></li>"+
|
||||
"</ul>"+
|
||||
(data.type==UI_CPAD?"<a class='confirm' onmousedown='padclick(CENTER,"+
|
||||
data.id+
|
||||
", true)' onmouseup='padclick(CENTER, "+
|
||||
data.id+
|
||||
", false)' id='pc"+
|
||||
data.id+
|
||||
"'>OK</a>":"")+
|
||||
"</nav>"+
|
||||
"</div>");$("#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:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter card-slider "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<div class='range-slider'>"+
|
||||
"<input id='sl"+
|
||||
data.id+
|
||||
"' type='range' min='0' max='100' value='"+
|
||||
data.value+
|
||||
"' "+elementStyle+" class='range-slider__range'>"+
|
||||
"<span class='range-slider__value'>"+
|
||||
data.value+
|
||||
"</span>"+
|
||||
"</div>"+
|
||||
"</div>");rangeSlider(!sliderContinuous);}
|
||||
break;case UI_NUMBER:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<input style='color:black;' "+elementStyle+" id='num"+
|
||||
data.id+
|
||||
"' type='number' value='"+
|
||||
data.value+
|
||||
"' onchange='numberchange("+
|
||||
data.id+
|
||||
")' />"+
|
||||
"</div>");}
|
||||
break;case UI_TEXT_INPUT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<input style='color:black;' "+elementStyle+" id='text"+
|
||||
data.id+
|
||||
"' value='"+
|
||||
data.value+
|
||||
"' onchange='textchange("+
|
||||
data.id+
|
||||
")' />"+
|
||||
"</div>");}
|
||||
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_SELECT:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<select style='color:black;' "+elementStyle+" id='select"+
|
||||
data.id+
|
||||
"' onchange='selectchange("+
|
||||
data.id+
|
||||
")' />"+
|
||||
"</div>");}
|
||||
break;case UI_OPTION:if(data.parentControl){var parent=$("#select"+data.parentControl);parent.append("<option id='option"+
|
||||
data.id+
|
||||
"' value='"+
|
||||
@ -195,78 +25,80 @@ data.selected+
|
||||
">"+
|
||||
data.label+
|
||||
"</option>");}
|
||||
break;case UI_MIN:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("min",data.value);}}
|
||||
break;case UI_MAX:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("max",data.value);}}
|
||||
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:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"<figure id='graph"+
|
||||
data.id+
|
||||
"'>"+
|
||||
"<figcaption>"+
|
||||
data.label+
|
||||
"</figcaption>"+
|
||||
"</figure>"+
|
||||
"</div>");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_GAUGE:var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"WILL BE A GAUGE <input style='color:black;' id='gauge"+
|
||||
data.id+
|
||||
"' type='number' value='"+
|
||||
data.value+
|
||||
"' onchange='numberchange("+
|
||||
data.id+
|
||||
")' />"+
|
||||
"</div>");}
|
||||
break;case UI_ACCEL:if(hasAccel)break;var parent;if(data.parentControl){parent=$("#tab"+data.parentControl);}else{parent=$("#row");}
|
||||
hasAccel=true;if(data.visible){parent.append("<div id='id"+
|
||||
data.id+
|
||||
"' "+panelStyle+" class='two columns card tcenter "+
|
||||
colorClass(data.color)+
|
||||
"'>"+
|
||||
"<h5>"+
|
||||
data.label+
|
||||
"</h5><hr/>"+
|
||||
"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+
|
||||
data.id+
|
||||
"' ><div class='ball"+
|
||||
data.id+
|
||||
"'></div><pre class='accelerometeroutput"+
|
||||
data.id+
|
||||
"'></pre>"+
|
||||
"</div>");requestOrientationPermission();}
|
||||
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:slider_move($("#id"+data.id),data.value,"100",false);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: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;default:console.error("Unknown type or event");break;}
|
||||
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.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));}}
|
||||
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);}
|
||||
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){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($("#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);}}
|
||||
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");}});};
|
||||
$(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;}}
|
BIN
docs/ui_colours.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 59 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: 21 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
docs/ui_separators.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
docs/ui_text.png
Before Width: | Height: | Size: 21 KiB 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';
|
||||
}
|
@ -31,7 +31,7 @@
|
||||
"frameworks": "arduino"
|
||||
}
|
||||
],
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "*"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=ESPUI
|
||||
version=2.1.0
|
||||
version=2.1.1
|
||||
author=Lukas Bachschwell
|
||||
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
|
||||
sentence=ESP32 and ESP8266 Web Interface Library
|
||||
|
445
src/ESPUI.cpp
@ -422,7 +422,7 @@ void onWsEvent(
|
||||
}
|
||||
#endif
|
||||
|
||||
ESPUI.jsonDom(client);
|
||||
ESPUI.jsonDom(0, client);
|
||||
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
@ -442,142 +442,155 @@ void onWsEvent(
|
||||
msg += (char)data[i];
|
||||
}
|
||||
|
||||
uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
|
||||
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||
if (msg.startsWith(F("uiok:")))
|
||||
{
|
||||
Serial.print(F("WS rec: "));
|
||||
Serial.println(msg);
|
||||
Serial.print(F("WS recognised ID: "));
|
||||
Serial.println(id);
|
||||
}
|
||||
#endif
|
||||
|
||||
Control* c = ESPUI.getControl(id);
|
||||
|
||||
if (c == nullptr)
|
||||
int idx = msg.substring(msg.indexOf(':') + 1).toInt();
|
||||
ESPUI.jsonDom(idx, client);
|
||||
} else
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
|
||||
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||
{
|
||||
Serial.print(F("No control found for ID "));
|
||||
Serial.print(F("WS rec: "));
|
||||
Serial.println(msg);
|
||||
Serial.print(F("WS recognised ID: "));
|
||||
Serial.println(id);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
Control* c = ESPUI.getControl(id);
|
||||
|
||||
if (c->callback == nullptr)
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
if (c == nullptr)
|
||||
{
|
||||
Serial.print(F("No callback found for ID "));
|
||||
Serial.println(id);
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
{
|
||||
Serial.print(F("No control found for ID "));
|
||||
Serial.println(id);
|
||||
}
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.startsWith(F("bdown:")))
|
||||
{
|
||||
c->callback(c, B_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("bup:")))
|
||||
{
|
||||
c->callback(c, B_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pfdown:")))
|
||||
{
|
||||
c->callback(c, P_FOR_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pfup:")))
|
||||
{
|
||||
c->callback(c, P_FOR_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pldown:")))
|
||||
{
|
||||
c->callback(c, P_LEFT_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("plup:")))
|
||||
{
|
||||
c->callback(c, P_LEFT_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("prdown:")))
|
||||
{
|
||||
c->callback(c, P_RIGHT_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("prup:")))
|
||||
{
|
||||
c->callback(c, P_RIGHT_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pbdown:")))
|
||||
{
|
||||
c->callback(c, P_BACK_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pbup:")))
|
||||
{
|
||||
c->callback(c, P_BACK_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pcdown:")))
|
||||
{
|
||||
c->callback(c, P_CENTER_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pcup:")))
|
||||
{
|
||||
c->callback(c, P_CENTER_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("sactive:")))
|
||||
{
|
||||
c->value = "1";
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_ACTIVE);
|
||||
}
|
||||
else if (msg.startsWith(F("sinactive:")))
|
||||
{
|
||||
c->value = "0";
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_INACTIVE);
|
||||
}
|
||||
else if (msg.startsWith(F("slvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, SL_VALUE);
|
||||
}
|
||||
else if (msg.startsWith(F("nvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, N_VALUE);
|
||||
}
|
||||
else if (msg.startsWith(F("tvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, T_VALUE);
|
||||
}
|
||||
else if (msg.startsWith("tabvalue:"))
|
||||
{
|
||||
c->callback(c, client->id());
|
||||
}
|
||||
else if (msg.startsWith(F("svalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_VALUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
if (c->callback == nullptr)
|
||||
{
|
||||
Serial.println(F("Malformated message from the websocket"));
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
{
|
||||
Serial.print(F("No callback found for ID "));
|
||||
Serial.println(id);
|
||||
}
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.startsWith(F("bdown:")))
|
||||
{
|
||||
c->callback(c, B_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("bup:")))
|
||||
{
|
||||
c->callback(c, B_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pfdown:")))
|
||||
{
|
||||
c->callback(c, P_FOR_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pfup:")))
|
||||
{
|
||||
c->callback(c, P_FOR_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pldown:")))
|
||||
{
|
||||
c->callback(c, P_LEFT_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("plup:")))
|
||||
{
|
||||
c->callback(c, P_LEFT_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("prdown:")))
|
||||
{
|
||||
c->callback(c, P_RIGHT_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("prup:")))
|
||||
{
|
||||
c->callback(c, P_RIGHT_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pbdown:")))
|
||||
{
|
||||
c->callback(c, P_BACK_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pbup:")))
|
||||
{
|
||||
c->callback(c, P_BACK_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("pcdown:")))
|
||||
{
|
||||
c->callback(c, P_CENTER_DOWN);
|
||||
}
|
||||
else if (msg.startsWith(F("pcup:")))
|
||||
{
|
||||
c->callback(c, P_CENTER_UP);
|
||||
}
|
||||
else if (msg.startsWith(F("sactive:")))
|
||||
{
|
||||
c->value = "1";
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_ACTIVE);
|
||||
}
|
||||
else if (msg.startsWith(F("sinactive:")))
|
||||
{
|
||||
c->value = "0";
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_INACTIVE);
|
||||
}
|
||||
else if (msg.startsWith(F("slvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, SL_VALUE);
|
||||
}
|
||||
else if (msg.startsWith(F("nvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, N_VALUE);
|
||||
}
|
||||
else if (msg.startsWith(F("tvalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, T_VALUE);
|
||||
}
|
||||
else if (msg.startsWith("tabvalue:"))
|
||||
{
|
||||
c->callback(c, client->id());
|
||||
}
|
||||
else if (msg.startsWith(F("svalue:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, S_VALUE);
|
||||
}
|
||||
else if (msg.startsWith(F("time:")))
|
||||
{
|
||||
c->value = msg.substring(msg.indexOf(':') + 1, msg.lastIndexOf(':'));
|
||||
ESPUI.updateControl(c, client->id());
|
||||
c->callback(c, TM_VALUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity)
|
||||
{
|
||||
Serial.println(F("Malformated message from the websocket"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -608,6 +621,8 @@ uint16_t ESPUIClass::addControl(ControlType type, const char* label, const Strin
|
||||
iterator->next = control;
|
||||
}
|
||||
|
||||
this->controlCount++;
|
||||
|
||||
return control->id;
|
||||
}
|
||||
|
||||
@ -622,13 +637,14 @@ bool ESPUIClass::removeControl(uint16_t id, bool force_reload_ui)
|
||||
{
|
||||
this->controls = it->next;
|
||||
delete it;
|
||||
this->controlCount--;
|
||||
if (force_reload_ui)
|
||||
{
|
||||
jsonReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonDom();
|
||||
jsonDom(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -644,13 +660,14 @@ bool ESPUIClass::removeControl(uint16_t id, bool force_reload_ui)
|
||||
{
|
||||
it->next = it_next->next;
|
||||
delete it_next;
|
||||
this->controlCount--;
|
||||
if (force_reload_ui)
|
||||
{
|
||||
jsonReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonDom(); // resends to all
|
||||
jsonDom(0); // resends to all
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -714,6 +731,10 @@ uint16_t ESPUIClass::gauge(const char* label, ControlColor color, int number, in
|
||||
return numberId;
|
||||
}
|
||||
|
||||
uint16_t ESPUIClass::separator(const char* label) {
|
||||
return addControl(ControlType::Separator, label, "", ControlColor::Alizarin, Control::noParent, nullptr);
|
||||
}
|
||||
|
||||
uint16_t ESPUIClass::accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color)
|
||||
{
|
||||
return addControl(ControlType::Accel, label, "", color, Control::noParent, callback);
|
||||
@ -748,6 +769,11 @@ void ESPUIClass::updateControl(Control* control, int clientId)
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->ws == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String json;
|
||||
DynamicJsonDocument document(jsonUpdateDocumentSize);
|
||||
JsonObject root = document.to<JsonObject>();
|
||||
@ -757,10 +783,13 @@ void ESPUIClass::updateControl(Control* control, int clientId)
|
||||
root["id"] = control->id;
|
||||
root["visible"] = control->visible;
|
||||
root["color"] = (int)control->color;
|
||||
if (control->panelStyle != 0)
|
||||
root["enabled"] = control->enabled;
|
||||
if (control->panelStyle.length())
|
||||
root["panelStyle"] = control->panelStyle;
|
||||
if (control->elementStyle != 0)
|
||||
if (control->elementStyle.length())
|
||||
root["elementStyle"] = control->elementStyle;
|
||||
if (control->inputType.length())
|
||||
root["inputType"] = control->inputType;
|
||||
serializeJson(document, json);
|
||||
|
||||
#if defined(DEBUG_ESPUI)
|
||||
@ -821,6 +850,43 @@ void ESPUIClass::setElementStyle(uint16_t id, String style, int clientId)
|
||||
}
|
||||
}
|
||||
|
||||
void ESPUIClass::setInputType(uint16_t id, String type, int clientId)
|
||||
{
|
||||
Control* control = getControl(id);
|
||||
if (control)
|
||||
{
|
||||
control->inputType = type;
|
||||
updateControl(control, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
void ESPUIClass::setPanelWide(uint16_t id, bool wide)
|
||||
{
|
||||
Control* control = getControl(id);
|
||||
if (control)
|
||||
{
|
||||
control->wide = wide;
|
||||
}
|
||||
}
|
||||
|
||||
void ESPUIClass::setEnabled(uint16_t id, bool enabled, int clientId) {
|
||||
Control* control = getControl(id);
|
||||
if (control)
|
||||
{
|
||||
control->enabled = enabled;
|
||||
updateControl(control, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ESPUIClass::setVertical(uint16_t id, bool vert) {
|
||||
Control* control = getControl(id);
|
||||
if (control)
|
||||
{
|
||||
control->vertical = vert;
|
||||
}
|
||||
}
|
||||
|
||||
void ESPUIClass::updateControl(uint16_t id, int clientId)
|
||||
{
|
||||
Control* control = getControl(id);
|
||||
@ -868,6 +934,15 @@ void ESPUIClass::updateControlValue(uint16_t id, const String& value, int client
|
||||
updateControlValue(control, value, clientId);
|
||||
}
|
||||
|
||||
void ESPUIClass::updateVisibility(uint16_t id, bool visibility, int clientId) {
|
||||
Control* control = getControl(id);
|
||||
if(control)
|
||||
{
|
||||
control->visible = visibility;
|
||||
updateControl(id);
|
||||
}
|
||||
}
|
||||
|
||||
void ESPUIClass::print(uint16_t id, const String& value)
|
||||
{
|
||||
updateControlValue(id, value);
|
||||
@ -878,6 +953,10 @@ void ESPUIClass::updateLabel(uint16_t id, const String& value)
|
||||
updateControlValue(id, value);
|
||||
}
|
||||
|
||||
void ESPUIClass::updateButton(uint16_t id, const String& value) {
|
||||
updateControlValue(id, value);
|
||||
}
|
||||
|
||||
void ESPUIClass::updateSlider(uint16_t id, int nValue, int clientId)
|
||||
{
|
||||
updateControlValue(id, String(nValue), clientId);
|
||||
@ -908,6 +987,11 @@ void ESPUIClass::updateGauge(uint16_t id, int number, int clientId)
|
||||
updateControlValue(id, String(number), clientId);
|
||||
}
|
||||
|
||||
void ESPUIClass::updateTime(uint16_t id, int clientId)
|
||||
{
|
||||
updateControl(id, clientId);
|
||||
}
|
||||
|
||||
void ESPUIClass::clearGraph(uint16_t id, int clientId) { }
|
||||
|
||||
void ESPUIClass::addGraphPoint(uint16_t id, int nValue, int clientId)
|
||||
@ -965,61 +1049,79 @@ void ESPUIClass::addGraphPoint(uint16_t id, int nValue, int clientId)
|
||||
tryId++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Convert & Transfer Arduino elements to JSON elements
|
||||
Initially this function used to send the control element data individually.
|
||||
Due to a change in the ESPAsyncWebserver library this had top be changed to be
|
||||
sent as one blob at the beginning. Therefore a new type is used as well
|
||||
Convert & Transfer Arduino elements to JSON elements. This function sends a chunk of
|
||||
JSON describing the controls of the UI, starting from the control at index startidx.
|
||||
If startidx is 0 then a UI_INITIAL_GUI message will be sent, else a UI_EXTEND_GUI.
|
||||
Both message types contain a list of serialised UI elements. Only a portion of the UI
|
||||
will be sent in order to avoid websocket buffer overflows. The client will acknowledge
|
||||
receipt of a partial message by requesting the next chunk of UI.
|
||||
|
||||
The protocol is:
|
||||
SERVER: jsonDom(0):
|
||||
"UI_INITIAL_GUI: n serialised UI elements"
|
||||
CLIENT: controls.js:handleEvent()
|
||||
"uiok:n"
|
||||
SERVER: jsonDom(n):
|
||||
"UI_EXTEND_GUI: n serialised UI elements"
|
||||
CLIENT: controls.js:handleEvent()
|
||||
"uiok:2*n"
|
||||
etc.
|
||||
*/
|
||||
void ESPUIClass::jsonDom(AsyncWebSocketClient* client)
|
||||
void ESPUIClass::jsonDom(uint16_t startidx, AsyncWebSocketClient* client)
|
||||
{
|
||||
if(startidx >= this->controlCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument document(jsonInitialDocumentSize);
|
||||
document["type"] = (int)UI_INITIAL_GUI;
|
||||
document["type"] = startidx ? (int)UI_EXTEND_GUI : (int)UI_INITIAL_GUI;
|
||||
document["sliderContinuous"] = sliderContinuous;
|
||||
document["startindex"] = startidx;
|
||||
document["totalcontrols"] = this->controlCount;
|
||||
JsonArray items = document.createNestedArray("controls");
|
||||
|
||||
Control* control = this->controls;
|
||||
|
||||
JsonObject titleItem = items.createNestedObject();
|
||||
titleItem["type"] = (int)UI_TITLE;
|
||||
titleItem["label"] = ui_title;
|
||||
|
||||
while (1)
|
||||
{
|
||||
control = prepareJSONChunk(client, control, &items);
|
||||
prepareJSONChunk(client, startidx, &items);
|
||||
|
||||
String json;
|
||||
serializeJson(document, json);
|
||||
String json;
|
||||
serializeJson(document, json);
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (this->verbosity >= Verbosity::VerboseJSON)
|
||||
{
|
||||
Serial.println("Sending elements --------->");
|
||||
Serial.println(json);
|
||||
}
|
||||
#endif
|
||||
if (client != nullptr)
|
||||
client->text(json);
|
||||
else
|
||||
this->ws->textAll(json);
|
||||
|
||||
if (control == nullptr)
|
||||
break;
|
||||
|
||||
document.clear();
|
||||
items.clear();
|
||||
document["type"] = (int)UI_EXTEND_GUI;
|
||||
items = document.createNestedArray("controls");
|
||||
if (this->verbosity >= Verbosity::VerboseJSON)
|
||||
{
|
||||
Serial.println("Sending elements --------->");
|
||||
Serial.println(json);
|
||||
}
|
||||
#endif
|
||||
if (client != nullptr)
|
||||
client->text(json);
|
||||
else
|
||||
this->ws->textAll(json);
|
||||
}
|
||||
|
||||
/* Prepare a chunk of elements as a single JSON string. If the allowed number of elements is greater than the total
|
||||
number this will represent the entire UI and this function will return null. If a control pointer is returned then the
|
||||
limit was reached, the currently serialised must be sent, and then processing resumed to send the next chunk. */
|
||||
Control* ESPUIClass::prepareJSONChunk(AsyncWebSocketClient* client, Control* control, JsonArray* items)
|
||||
number this will represent the entire UI. More likely, it will represent a small section of the UI to be sent. The client
|
||||
will acknoledge receipt by requesting the next chunk. */
|
||||
void ESPUIClass::prepareJSONChunk(AsyncWebSocketClient* client, uint16_t startindex, JsonArray* items)
|
||||
{
|
||||
int elementcount = 0;
|
||||
//First check that there will be sufficient nodes in the list
|
||||
if(startindex >= this->controlCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Follow the list until control points to the startindex'th node
|
||||
Control* control = this->controls;
|
||||
for(auto i = 0; i < startindex; i++) {
|
||||
control = control->next;
|
||||
}
|
||||
|
||||
//To prevent overflow, keep track of the number of elements we have serialised into this message
|
||||
int elementcount = 0;
|
||||
while (control != nullptr && elementcount < 10)
|
||||
{
|
||||
JsonObject item = items->createNestedObject();
|
||||
@ -1030,10 +1132,17 @@ Control* ESPUIClass::prepareJSONChunk(AsyncWebSocketClient* client, Control* con
|
||||
item["value"] = String(control->value);
|
||||
item["color"] = (int)control->color;
|
||||
item["visible"] = (int)control->visible;
|
||||
if (control->panelStyle != 0)
|
||||
item["enabled"] = control->enabled;
|
||||
if (control->panelStyle.length())
|
||||
item["panelStyle"] = String(control->panelStyle);
|
||||
if (control->elementStyle != 0)
|
||||
if (control->elementStyle.length())
|
||||
item["elementStyle"] = String(control->elementStyle);
|
||||
if (control->inputType.length())
|
||||
item["inputType"] = String(control->inputType);
|
||||
if (control->wide == true)
|
||||
item["wide"] = true;
|
||||
if (control->vertical == true)
|
||||
item["vertical"] = true;
|
||||
|
||||
if (control->parentControl != Control::noParent)
|
||||
{
|
||||
@ -1057,7 +1166,7 @@ Control* ESPUIClass::prepareJSONChunk(AsyncWebSocketClient* client, Control* con
|
||||
control = control->next;
|
||||
elementcount++;
|
||||
}
|
||||
return control;
|
||||
return;
|
||||
}
|
||||
|
||||
void ESPUIClass::jsonReload()
|
||||
|
28
src/ESPUI.h
@ -55,6 +55,8 @@ enum ControlType : uint8_t
|
||||
Step,
|
||||
Gauge,
|
||||
Accel,
|
||||
Separator,
|
||||
Time,
|
||||
|
||||
UpdateOffset = 100,
|
||||
UpdatePad = 101,
|
||||
@ -74,6 +76,8 @@ enum ControlType : uint8_t
|
||||
UpdateStep,
|
||||
UpdateGauge,
|
||||
UpdateAccel,
|
||||
UpdateSeparator,
|
||||
UpdateTime,
|
||||
|
||||
InitialGui = 200,
|
||||
Reload = 201,
|
||||
@ -136,9 +140,13 @@ public:
|
||||
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;
|
||||
@ -151,6 +159,9 @@ public:
|
||||
value(value),
|
||||
color(color),
|
||||
visible(visible),
|
||||
wide(false),
|
||||
vertical(false),
|
||||
enabled(true),
|
||||
parentControl(parentControl),
|
||||
next(nullptr)
|
||||
{
|
||||
@ -195,6 +206,7 @@ private:
|
||||
#define N_VALUE 9
|
||||
#define T_VALUE 10
|
||||
#define S_VALUE 11
|
||||
#define TM_VALUE 12
|
||||
|
||||
enum Verbosity : uint8_t
|
||||
{
|
||||
@ -257,6 +269,7 @@ public:
|
||||
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
|
||||
|
||||
// Input only
|
||||
uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color);
|
||||
@ -274,24 +287,33 @@ public:
|
||||
|
||||
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(AsyncWebSocketClient* client = nullptr);
|
||||
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr);
|
||||
|
||||
Verbosity verbosity;
|
||||
|
||||
@ -303,7 +325,9 @@ private:
|
||||
const char* basicAuthPassword = nullptr;
|
||||
bool basicAuth = true;
|
||||
|
||||
Control* prepareJSONChunk(AsyncWebSocketClient* client, Control* control, JsonArray* items);
|
||||
uint16_t controlCount = 0;
|
||||
|
||||
void prepareJSONChunk(AsyncWebSocketClient* client, uint16_t startindex, JsonArray* items);
|
||||
};
|
||||
|
||||
extern ESPUIClass ESPUI;
|
||||
|
@ -16,4 +16,4 @@ function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(r
|
||||
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,19,56,231,94,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 };
|
||||
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 };
|
||||
|
@ -2,4 +2,4 @@ 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,19,56,231,94,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 };
|
||||
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 };
|
||||
|
@ -2,4 +2,4 @@ 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,19,56,231,94,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 };
|
||||
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 };
|
||||
|
@ -12,4 +12,4 @@ function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
|
||||
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,150,187,70,95,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 };
|
||||
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 };
|
||||
|