mirror of
				https://github.com/s00500/ESPUI.git
				synced 2025-10-31 02:03:25 +00:00 
			
		
		
		
	Merge pull request #147 from iangray001/designupdates
Separators, grouped controls, and wide controls
This commit is contained in:
		
							
								
								
									
										58
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								README.md
									
									
									
									
									
								
							| @@ -33,6 +33,8 @@ The Library runs fine on any kind of **ESP8266** and **ESP32** (NodeMCU Boards, | ||||
| - 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 | ||||
|  | ||||
| ## Further Roadmap | ||||
|  | ||||
| @@ -305,6 +307,18 @@ Then all widgets for the tab need to be added to it by specifying the tab as the | ||||
|  | ||||
| `ESPUI.addControl( ControlType::Text, "Text Title:", "a Text Field", ControlColor::Alizarin, tab1, &textCall );` | ||||
|  | ||||
| ### 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); | ||||
| ``` | ||||
|  | ||||
| ### Initialisation of the UI | ||||
|  | ||||
| After all the elements are configured you can use `ESPUI.begin("Some Title");` | ||||
| @@ -390,6 +404,50 @@ Note: The images in this example are formed by setting a Label to contain an `<i | ||||
|  ESPUI.addControl(ControlType::Label, "Label", "<img src='path/to/image'>", ControlColor::Peterriver); | ||||
| ``` | ||||
|  | ||||
| ### 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. | ||||
|  | ||||
|  | ||||
|  | ||||
| If you group too many elements it might throw the layout of the rest of the UI out of line. Consider adding separators to correct this. | ||||
|  | ||||
| ### Advanced: 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. | ||||
|  | ||||
| # Notes for Development | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| @@ -423,6 +445,8 @@ button:active { | ||||
| button, | ||||
| .button { | ||||
|   margin-bottom: 1rem; | ||||
|   margin-left: 0.3rem; | ||||
|   margin-right: 0.3rem; | ||||
| } | ||||
|  | ||||
| /* Utilities | ||||
| @@ -576,7 +600,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 +958,7 @@ input[id^="num"] { | ||||
|  | ||||
| body div>ul.navigation { | ||||
|   margin: 0; | ||||
|   margin-bottom: 30px; | ||||
|   padding: 0; | ||||
|   border-bottom: 3px solid #666; | ||||
|   overflow: hidden; | ||||
|   | ||||
							
								
								
									
										2
									
								
								data/css/style.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								data/css/style.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										651
									
								
								data/js/controls.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										651
									
								
								data/js/controls.js
									
									
									
									
										vendored
									
									
								
							| @@ -52,7 +52,10 @@ 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 UP = 0; | ||||
| const DOWN = 1; | ||||
| @@ -233,9 +236,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 | ||||
| @@ -272,366 +272,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: | ||||
| @@ -677,35 +441,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 +460,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')) { | ||||
| @@ -964,3 +657,109 @@ var rangeSlider = function (isDiscrete) { | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
| var addToHTML = function(data) { | ||||
|   panelStyle = data.hasOwnProperty('panelStyle') ? " style='" + data.panelStyle + "' " : ""; | ||||
|   elementStyle = data.hasOwnProperty('elementStyle') ? " style='" + data.elementStyle + "' " : ""; | ||||
|   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.type, data.id, data.value, elementStyle) + | ||||
|         "</div>"; | ||||
|         break; | ||||
|       case UI_SEPARATOR: | ||||
|         html = "<div id='id" + data.id + "' " + panelStyle + " class='sectionbreak columns'>" + | ||||
|         "<h5>" + data.label + "</h5><hr/></div>"; | ||||
|         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.type, data.id, data.value, elementStyle)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| var elementHTML = function(type, id, value, elementStyle) { | ||||
|   switch(type) { | ||||
|     case UI_LABEL: | ||||
|       return "<span id='l" + id + "' " + elementStyle +  | ||||
|         " class='label label-wrap'>" + value + "</span>"; | ||||
|     case UI_BUTTON: | ||||
|       return "<button id='btn" + id + "' " + elementStyle +  | ||||
|         " onmousedown='buttonclick(" + id + ", true)'" + | ||||
|         " onmouseup='buttonclick(" + id + ", false)'>" + | ||||
|         value + "</button>"; | ||||
|     case UI_SWITCHER: | ||||
|       return "<label id='sl" + id + "' " + elementStyle +  | ||||
|       " class='switch " + (value == "1" ? "checked" : "") + "'>" + | ||||
|       "<div class='in'>" +  | ||||
|       "<input type='checkbox' id='s" + id + "' onClick='switcher(" + id + ",null)' " +  | ||||
|       (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>" + | ||||
|         (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'>" +  | ||||
|         "<input id='sl" + id + "' type='range' min='0' max='100' value='" + value + "' " +  | ||||
|         elementStyle + " class='range-slider__range'><span class='range-slider__value'>" +  | ||||
|         value + "</span></div>"; | ||||
|     case UI_NUMBER: | ||||
|       return "<input style='color:black;' " + elementStyle + " id='num" + id +  | ||||
|         "' type='number' value='" + value + "' onchange='numberchange(" + id + ")' />"; | ||||
|     case UI_TEXT_INPUT: | ||||
|       return "<input style='color:black;' " + elementStyle + " id='text" + id + | ||||
|         "' value='" + 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>" + label + "</figcaption></figure>"; | ||||
|     case UI_GAUGE: | ||||
|       return "WILL BE A GAUGE <input style='color:black;' id='gauge" + id +  | ||||
|         "' type='number' value='" + 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 ""; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										273
									
								
								data/js/controls.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										273
									
								
								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 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"";}} | ||||
| 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,13 @@ 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);});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: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='"+ | ||||
| @@ -198,57 +26,8 @@ data.label+ | ||||
| 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_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);} | ||||
| @@ -269,4 +48,38 @@ function buttonclick(number,isdown){if(isdown)websock.send("bdown:"+number);else | ||||
| 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);}} | ||||
| 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+"' ":"";elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"' ":"";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.type,data.id,data.value,elementStyle)+ | ||||
| "</div>";break;case UI_SEPARATOR:html="<div id='id"+data.id+"' "+panelStyle+" class='sectionbreak columns'>"+ | ||||
| "<h5>"+data.label+"</h5><hr/></div>";break;} | ||||
| parent.append(html);}else{var parent=$("#id"+data.parentControl);parent.append(elementHTML(data.type,data.id,data.value,elementStyle));}} | ||||
| var elementHTML=function(type,id,value,elementStyle){switch(type){case UI_LABEL:return"<span id='l"+id+"' "+elementStyle+ | ||||
| " class='label label-wrap'>"+value+"</span>";case UI_BUTTON:return"<button id='btn"+id+"' "+elementStyle+ | ||||
| " onmousedown='buttonclick("+id+", true)'"+ | ||||
| " onmouseup='buttonclick("+id+", false)'>"+ | ||||
| value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementStyle+ | ||||
| " class='switch "+(value=="1"?"checked":"")+"'>"+ | ||||
| "<div class='in'>"+ | ||||
| "<input type='checkbox' id='s"+id+"' onClick='switcher("+id+",null)' "+ | ||||
| (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>"+ | ||||
| (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'>"+ | ||||
| "<input id='sl"+id+"' type='range' min='0' max='100' value='"+value+"' "+ | ||||
| elementStyle+" class='range-slider__range'><span class='range-slider__value'>"+ | ||||
| value+"</span></div>";case UI_NUMBER:return"<input style='color:black;' "+elementStyle+" id='num"+id+ | ||||
| "' type='number' value='"+value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input style='color:black;' "+elementStyle+" id='text"+id+ | ||||
| "' value='"+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>"+label+"</figcaption></figure>";case UI_GAUGE:return"WILL BE A GAUGE <input style='color:black;' id='gauge"+id+ | ||||
| "' type='number' value='"+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"";}} | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/ui_groupedbuttons.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/ui_groupedbuttons.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 24 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/ui_groupedbuttons2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/ui_groupedbuttons2.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 43 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/ui_separators.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/ui_separators.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/ui_widecontrols.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/ui_widecontrols.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 22 KiB | 
| @@ -714,6 +714,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); | ||||
| @@ -821,6 +825,14 @@ void ESPUIClass::setElementStyle(uint16_t id, String style, int clientId) | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ESPUIClass::setPanelWide(uint16_t id, bool wide) { | ||||
|     Control* control = getControl(id); | ||||
|     if (control) | ||||
|     { | ||||
|         control->wide = wide; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ESPUIClass::updateControl(uint16_t id, int clientId) | ||||
| { | ||||
|     Control* control = getControl(id); | ||||
| @@ -1034,6 +1046,8 @@ Control* ESPUIClass::prepareJSONChunk(AsyncWebSocketClient* client, Control* con | ||||
|             item["panelStyle"] = String(control->panelStyle); | ||||
|         if (control->elementStyle != 0) | ||||
|             item["elementStyle"] = String(control->elementStyle); | ||||
|         if (control->wide == true) | ||||
|             item["wide"] = true; | ||||
|  | ||||
|         if (control->parentControl != Control::noParent) | ||||
|         { | ||||
|   | ||||
| @@ -55,6 +55,7 @@ enum ControlType : uint8_t | ||||
|     Step, | ||||
|     Gauge, | ||||
|     Accel, | ||||
|     Separator, | ||||
|  | ||||
|     UpdateOffset = 100, | ||||
|     UpdatePad = 101, | ||||
| @@ -74,6 +75,7 @@ enum ControlType : uint8_t | ||||
|     UpdateStep, | ||||
|     UpdateGauge, | ||||
|     UpdateAccel, | ||||
|     UpdateSeparator, | ||||
|  | ||||
|     InitialGui = 200, | ||||
|     Reload = 201, | ||||
| @@ -136,6 +138,7 @@ public: | ||||
|     String value; | ||||
|     ControlColor color; | ||||
|     bool visible; | ||||
|     bool wide; | ||||
|     uint16_t parentControl; | ||||
|     String panelStyle; | ||||
|     String elementStyle; | ||||
| @@ -151,6 +154,7 @@ public: | ||||
|           value(value), | ||||
|           color(color), | ||||
|           visible(visible), | ||||
|           wide(false), | ||||
|           parentControl(parentControl), | ||||
|           next(nullptr) | ||||
|     { | ||||
| @@ -257,6 +261,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); | ||||
| @@ -287,6 +292,8 @@ public: | ||||
|     void setPanelStyle(uint16_t id, String style, int clientId = -1); | ||||
|     void setElementStyle(uint16_t id, String style, int clientId = -1); | ||||
|  | ||||
|     void setPanelWide(uint16_t id, bool wide); | ||||
|  | ||||
|     // Variables | ||||
|     const char* ui_title = "ESPUI"; // Store UI Title and Header Name | ||||
|     Control* controls = nullptr; | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user
	 Ian Gray
					Ian Gray