From 0086099d93d6a95af9cdf6fbeb467a0c0ef395b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Thu, 7 Sep 2017 16:18:11 +0200 Subject: [PATCH] espterm demo html --- README.md | 2 - about.html | 114 ++ cfg_network.html | 140 +++ cfg_system.html | 139 +++ cfg_term.html | 277 +++++ cfg_wifi.html | 165 +++ cfg_wifi_conn.html | 120 ++ css/app.css | 1810 +++++++++++++++++++++++++++++ favicon.ico | Bin 0 -> 318 bytes help.html | 959 ++++++++++++++++ img/cvut.svg | 5 + img/loader.gif | Bin 0 -> 2608 bytes img/vt100.jpg | Bin 0 -> 11088 bytes index.html | 2 +- js/app.js | 2533 +++++++++++++++++++++++++++++++++++++++++ network_set.html | 1 + reset_screen.html | 1 + restore_defaults.html | 1 + restore_hard.html | 1 + system_set.html | 1 + term.html | 123 ++ term_set.html | 1 + wifi_connstatus.html | 1 + wifi_scan.html | 1 + wifi_set.html | 1 + write_defaults.html | 1 + 26 files changed, 6396 insertions(+), 3 deletions(-) delete mode 100644 README.md create mode 100644 about.html create mode 100644 cfg_network.html create mode 100644 cfg_system.html create mode 100644 cfg_term.html create mode 100644 cfg_wifi.html create mode 100644 cfg_wifi_conn.html create mode 100644 css/app.css create mode 100644 favicon.ico create mode 100644 help.html create mode 100755 img/cvut.svg create mode 100755 img/loader.gif create mode 100644 img/vt100.jpg create mode 100644 js/app.js create mode 100644 network_set.html create mode 100644 reset_screen.html create mode 100644 restore_defaults.html create mode 100644 restore_hard.html create mode 100644 system_set.html create mode 100644 term.html create mode 100644 term_set.html create mode 100644 wifi_connstatus.html create mode 100644 wifi_scan.html create mode 100644 wifi_set.html create mode 100644 write_defaults.html diff --git a/README.md b/README.md deleted file mode 100644 index a8d6924..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# espterm.github.io -ESPTerm website diff --git a/about.html b/about.html new file mode 100644 index 0000000..af1a81b --- /dev/null +++ b/about.html @@ -0,0 +1,114 @@ + + + + + + + About ESPTerm :: ESPTerm + + + + + +
+ + + + +
+Loading… +

About ESPTerm

+ + + +
+ +

ESP8266 Remote Terminal

+ + + +

+ © Ondřej Hruška, 2016-2017 + <ondra@ondrovo.com> +

+ +

+ Katedra měření, FEL ČVUT
+ Department of Measurement, FEE CTU +

+
+ +
+

Version

+ + + + + + + + + + + + + +
ESPTermv1.2.3, built 2017-09-07 at 16:17
libesphttpdv4.5.6
ESP IoT SDKv1.52
+
+ +
+

Issues

+

+ Please report any issues to the bugtracker or send them by e-mail (see above). +

+

+ Firmware updates can be downloaded from the releases page and flashed + with esptool.py. +

+
+ +
+

Contributing

+

+ You're welcome to submit your improvements and ideas to our GitHub repository! +

+ +

+ If you'd like to donate, please try PayPal or + LiberaPay. +

+
+ +
+

Thanks

+

+ The webserver is based on a fork of the + esphttpd library by Jeroen Domburg (Sprite_tm). +

+

+ Using (modified) JS library chibi.js by + Kyle Barrow as a lightweight jQuery alternative. +

+
+ +
+ + + +
+ +
+ + + diff --git a/cfg_network.html b/cfg_network.html new file mode 100644 index 0000000..49bcb8c --- /dev/null +++ b/cfg_network.html @@ -0,0 +1,140 @@ + + + + + + + Network Settings :: ESPTerm + + + + + +
+ + + + +
+Loading… +

Network Settings

+ + + + +
+

DHCP Client (Station)

+ +
+ + Switch off Dynamic IP to configure the static IP address.
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Apply! +
+
+ +
+

DHCP Server (AP)

+ +
+ + Those settings affect the built-in DHCP server in AP mode.
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +  min +
+ +
+ Apply! +
+
+ +
+

MAC addresses

+ +
+ +
+
+ +
+
+ + + +
+ + + +
+ +
+ + + diff --git a/cfg_system.html b/cfg_system.html new file mode 100644 index 0000000..96a5fa7 --- /dev/null +++ b/cfg_system.html @@ -0,0 +1,139 @@ + + + + + + + System Settings :: ESPTerm + + + + + +
+ + + + +
+Loading… +

System Settings

+ + + +
+

Save & Restore

+ +
+ + ESPTerm contains two persistent memory banks, one for default and + one for active settings. Active settings can be stored as defaults + by the administrator (password required). +
+ + + + + + +
+ + +
+

Serial Port

+ +
+ + This form controls the primary, communication UART. The debug UART is fixed at 115.200 baud, one stop-bit and no parity. +
+ +
+ + +  bps +
+ +
+ + +
+ +
+ + +
+ +
+ Apply! +
+
+ + + +
+ + + +
+ +
+ + + diff --git a/cfg_term.html b/cfg_term.html new file mode 100644 index 0000000..0f20fda --- /dev/null +++ b/cfg_term.html @@ -0,0 +1,277 @@ + + + + + + + Terminal Settings :: ESPTerm + + + + + +
+ + + + +
+Loading… +

Terminal Settings

+ + + + + +
+

Initial Settings

+ +
+ + Those are the initial settings used after ESPTerm powers on or when the screen + reset command is received. Some options can be changed by the application via escape sequences, + those changes won't be saved in Flash. +
+ +
+ + +
+ +
+
+ 3031323334353637 +
+ +
+ 9091929394959697 +
+ +
+ 4041424344454647 +
+ +
+ 100101102103104105106107 +
+
+ +
+
+ Default colors preview
+
+ +
+ +   +
+ +
+ +   +
+ +
+ + +
+ +
+ +   +   +   +   + +
+ +
+ Apply! +
+
+ +
+

Expert Options

+ +
+ + Those are advanced config options that usually don't need to be changed. + Edit them only if you know what you're doing.
+ +
+ + +  ms +
+ +
+ + +  ms +
+ +
+ + +  ms +
+ +
+ +   +   +   +   + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Apply! +
+
+ + + +
+ + + +
+ +
+ + + diff --git a/cfg_wifi.html b/cfg_wifi.html new file mode 100644 index 0000000..48753a2 --- /dev/null +++ b/cfg_wifi.html @@ -0,0 +1,165 @@ + + + + + + + WiFi Settings :: ESPTerm + + + + + +
+ + + + +
+Loading… +

WiFi Settings

+ + + +
+

Built-in Access Point

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ Apply! +
+
+ +
+

Join Existing Network

+ +
+ + +
+ +
+ + After selecting a network, press Apply to connect.
+ + + + +
+ + +
+ None
+
+ +
+ + + + +
+ +
+ Apply! +
+
+ + + +
+ + + +
+ +
+ + + diff --git a/cfg_wifi_conn.html b/cfg_wifi_conn.html new file mode 100644 index 0000000..989dd23 --- /dev/null +++ b/cfg_wifi_conn.html @@ -0,0 +1,120 @@ + + + + + + + Connecting to Network :: ESPTerm + + + + + +
+ +
+Loading… +

Connecting to Network

+ +
+

Status: .

+ Back to WiFi config +
+ +
+

+ If you're configuring ESPTerm via a smartphone, or were connected + from another external network, your device may lose connection and this + progress indicator won't work. Please wait a while (~ 15 seconds), + then check if the connection succeeded.

+

+ To force enable the built-in AP, hold the BOOT + button until the blue LED starts flashing. Hold the button longer (until the LED + flashes rapidly) for a "factory reset".

+
+ + + +
+ + + +
+ +
+ + + diff --git a/css/app.css b/css/app.css new file mode 100644 index 0000000..a25946a --- /dev/null +++ b/css/app.css @@ -0,0 +1,1810 @@ +@charset "UTF-8"; +/* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ +*, *:before, *:after { + box-sizing: border-box; } + +html { + font-family: sans-serif; + /* 1 */ + -ms-text-size-adjust: 100%; + /* 2 */ + -webkit-text-size-adjust: 100%; + /* 2 */ } + +/** + * Remove default margin. + */ +body { + margin: 0; } + +/* HTML5 display definitions + ========================================================================== */ +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ +figure, +nav + { + display: block; } + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ +canvas, +progress + { + display: inline-block; + /* 1 */ + vertical-align: baseline; + /* 2 */ } + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ +[hidden] + { + display: none; } + +/* Links + ========================================================================== */ +/** + * Remove the gray background color from active links in IE 10. + */ +a { + background-color: transparent; } + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ +a:active, +a:hover { + outline: 0; } + +/* Text-level semantics + ========================================================================== */ +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ +b + { + font-weight: bold; } + +/** + * Address styling not present in Safari and Chrome. + */ +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ +h1 { + font-size: 2em; + margin: 0.67em 0; } + +h2 { + font-size: 2em; + margin: 0.67em 0; } + +/** + * Address styling not present in IE 8/9. + */ +/** + * Address inconsistent and variable font size in all browsers. + */ +small { + font-size: 80%; } + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +/* Embedded content + ========================================================================== */ +/** + * Remove border when inside `a` element in IE 8/9/10. + */ +img { + border: 0; } + +/** + * Correct overflow not hidden in IE 9/10/11. + */ +svg:not(:root) { + overflow: hidden; } + +/* Grouping content + ========================================================================== */ +/** + * Address margin not present in IE 8/9 and Safari. + */ +/** + * Address differences between Firefox and other browsers. + */ +hr { + box-sizing: content-box; + height: 0; } + +/** + * Contain overflow in all browsers. + */ +pre { + overflow: auto; } + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ +code, +pre + { + font-family: "DejaVu Sans Mono", "Inconsolata", monospace; + font-size: 1em; } + +/* Forms + ========================================================================== */ +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ +button, +input, +select, +textarea { + color: inherit; + /* 1 */ + font: inherit; + /* 2 */ + margin: 0; + /* 3 */ } + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ +button { + overflow: visible; } + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ +button, +select { + text-transform: none; } + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ +button, +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + /* 2 */ + cursor: pointer; + /* 3 */ } + +/** + * Re-set default cursor for disabled elements. + */ +button[disabled], +html input[disabled] { + cursor: default; } + +/** + * Remove inner padding and border in Firefox 4+. + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; } + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ +input { + line-height: normal; } + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ +/** + * Define consistent border, margin, and padding. + */ +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ +legend { + border: 0; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ +textarea { + overflow: auto; } + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ +/* Tables + ========================================================================== */ +/** + * Remove most spacing between table cells. + */ +table { + border-collapse: collapse; + border-spacing: 0; } + +td, +th { + padding: 0; } + +/* Fontello data, processed by the unpack script. */ +@font-face { + font-family: 'fontello'; + src: url("data:application/octet-stream;base64,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") format("woff"); } +[class^="icn-"]::before, [class*=" icn-"]::before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } + +/* Fontello icon codes */ +/* Fontello classes */ +.icn-keyboard::before { + content: '\2328'; } + +.icn-about::before { + content: '\2605'; } + +.icn-restore::before { + content: '\267c'; } + +.icn-configure::before { + content: '\2699'; } + +.icn-ok::before { + content: '\2714'; } + +.icn-help::before { + content: '\2753'; } + +.icn-donate::before { + content: '\2764'; } + +.icn-back::before { + content: '\276e'; } + +.icn-cancel::before { + content: '\e801'; } + +.icn-paste::before { + content: '\f0ea'; } + +.icn-network::before { + content: '🌍'; } + +.icn-github::before { + content: '🐱'; } + +.icn-persist::before { + content: '💾'; } + +.icn-download::before { + content: '📥'; } + +.icn-wifi::before { + content: '📶'; } + +.icn-terminal::before { + content: '🖳'; } + +html { + box-sizing: border-box; } + +*, *::after, *::before { + box-sizing: inherit; } + +html { + font-family: Arial, sans-serif; + color: #D0D0D0; + background: #131315; } + +html, body { + border: 0 none; + margin: 0; + padding: 0; + text-decoration: none; + width: 100%; + height: 100%; + overflow: hidden; } + +a, a:visited, a:link { + cursor: pointer; + color: #5abfff; + text-decoration: none; } + +a:hover { + color: #5abfff; + text-decoration: underline; } + +.hidden { + display: none !important; } + +[onclick] { + cursor: pointer; } + +ul > * { + padding-top: .3em; + padding-bottom: .3em; } + +ul { + margin-top: 0; + margin-bottom: 0; } + +/* Main outer container */ +#outer { + display: flex; + position: absolute; + width: 100%; + height: 100%; + left: 0; + right: 0; + top: 0; + bottom: 0; + overflow: hidden; + flex-direction: row; } + +@media screen and (max-width: 544px) { + #outer { + display: block; + overflow-y: scroll; } } +#menu { + flex: 0 0 15rem; + background: #3983CD; } + #menu > * { + display: block; + text-decoration: none; + padding: 0.61805rem 1rem; + white-space: nowrap; + word-wrap: normal; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + #menu #brand { + cursor: pointer; + color: white; + background: #2b6aa8; + font-size: 120%; + text-align: center; + position: relative; + transition: none; + font-weight: bold; + margin-bottom: 1rem; } + @media screen and (max-width: 544px) { + #menu #brand { + background: #3983CD; + cursor: pointer; + margin-bottom: 0.38198rem; } + #menu #brand::after { + position: absolute; + color: rgba(0, 0, 0, 0.4); + right: 1rem; + content: '▸'; + top: 50%; + font-size: 120%; + font-weight: bold; + transform: translate(0, -50%) rotate(90deg); } } + #menu.expanded #brand { + background: #2b6aa8; } + @media screen and (max-width: 544px) { + #menu.expanded #brand:after { + transform: translate(-25%, -50%) rotate(-90deg); } } + #menu a { + font-size: 130%; + color: white; + transition: background-color 0.2s; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.4); } + #menu a:hover, #menu a.selected { + background: #5badff; + text-shadow: 0 0 5px rgba(0, 0, 0, 0.6); } + #menu a.selected { + position: relative; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); } + #menu a:focus { + outline-color: #ff0099; } + #menu a::before { + vertical-align: -2px; + margin-left: 0; + margin-right: 15px; } + @media screen and (max-width: 544px) { + #menu a { + display: none; } + #menu a::before { + margin-left: 10px; } } + #menu.expanded a { + display: block; } + @media screen and (min-width: 545px) and (max-width: 1000px) { + #menu { + flex-basis: 10rem; } + #menu #brand { + font-size: 95%; + margin-bottom: 0.61805rem; } + #menu a { + font-size: 105%; } + #menu > * { + padding: 0.38198rem 0.61805rem; } } + +#content { + flex-grow: 1; + position: relative; + padding: 1rem; + overflow-y: auto; } + @media screen and (max-width: 544px) { + #content { + padding: 0.61805rem; } } + #content > * { + margin-left: auto; + margin-right: auto; } + #content h1 { + text-align: center; + font-size: 2.2807em; + margin-top: 0; + margin-bottom: 1rem; } + #content h2 { + font-size: 1.42383em; + margin-bottom: 0.61805rem; } + @media screen and (max-width: 544px) { + #content h1 { + font-size: 1.80203em; + margin-bottom: 0.61805rem; } + #content h2 { + font-size: 1.26563em; + margin-bottom: 0.61805rem; } } + #content td, #content th { + padding: 0.38198rem; } + #content tbody th { + text-align: right; + width: 160px; + color: white; } + +#content { + opacity: 0; + transition: opacity 0.15s ease-in; } + +#content.load { + opacity: 1; } + +#loader { + position: absolute; + right: 1.618rem; + top: 1.618rem; + transition: opacity .2s; + opacity: 0; } + @media screen and (max-width: 544px) { + #loader { + top: 1rem; + right: 1rem; } } + #loader.show { + opacity: 1; } + +.botpad { + display: block; + height: 5em; } + +.Box { + display: block; + max-width: 900px; + margin-top: 1rem; + padding: 0.61805rem 1rem; + border-radius: 3px; + background-color: rgba(255, 255, 255, 0.07); + box-shadow: 0 0 4px black; + border: 1px solid #4f4f4f; } + .Box::after { + content: ''; + display: block; + clear: both; } + @media screen and (max-width: 544px) { + .Box { + margin-top: 0.61805rem; } } + .Box h1, .Box h2 { + overflow: hidden; } + h1 + .Box { + margin-top: 0; } + .Box h2 { + margin-top: 0; + margin-bottom: 0 !important; } + .Box p:last-child { + margin-bottom: 0.5em; } + .Box.wide { + width: initial; + max-width: initial; } + .Box.medium { + max-width: 1200px; } + .Box.str { + position: relative; } + .Box.str .Row.buttons { + position: absolute; } + @media screen and (max-width: 544px) { + .Box.str .Row.buttons { + right: 1rem; + top: 1.8em; + margin: 1rem auto; } } + @media screen and (min-width: 545px) { + .Box.str .Row.buttons { + right: 0; + top: 0; + margin-top: 0.61805rem; } } + .Box.str.mobopen .Row.buttons { + top: 0; + margin-top: 0.61805rem; } + .Box .Row.explain { + display: block; + max-width: 600px; + margin-left: 0; + line-height: 1.2; } + @media screen and (max-width: 544px) { + .Box .Row.explain { + margin-top: 60px; } } + .Box .Row.explain.nomargintop { + margin-top: 12px !important; } + .Box.mobopen .Row.explain { + margin-top: 12px; } + @media screen and (max-width: 544px) { + .Box.mobopen .Row.explain { + margin-top: 18px; } } + +.Box.fold h2 { + position: relative; + cursor: pointer; + padding: 2px 1.3rem 2px 5px; + margin: 0 -5px 0 -5px; } + .Box.fold h2::after { + position: absolute; + right: 4px; + content: '▸'; + top: 50%; + font-size: 120%; + font-weight: bold; + transform: translate(0, -50%) rotate(90deg); } +.Box.fold.expanded h2::after { + transform: translate(-25%, -50%) rotate(-90deg); + margin-bottom: 1rem; } +.Box.fold .Row { + display: none; } +.Box.fold.expanded .Row { + display: flex; } + .Box.fold.expanded .Row.v { + display: block; } + +@media screen and (max-width: 544px) { + .Box.fold h2, .Box.mobcol h2 { + padding: 2px 1.3rem 2px 5px; + margin: 0 -5px 0 -5px; } + .Box.fold.expanded h2::after, .Box.mobcol.expanded h2::after { + margin-bottom: 1rem; } + + .Box.mobcol h2 { + position: relative; + cursor: pointer; + padding: 2px 1.3rem 2px 5px; + margin: 0 -5px 0 -5px; } + .Box.mobcol h2::after { + position: absolute; + right: 4px; + content: '▸'; + top: 50%; + font-size: 120%; + font-weight: bold; + transform: translate(0, -50%) rotate(90deg); } + .Box.mobcol.expanded h2::after { + transform: translate(-25%, -50%) rotate(-90deg); + margin-bottom: 1rem; } + .Box.mobcol .Row { + display: none; } + .Box.mobcol.expanded .Row { + display: flex; } + .Box.mobcol.expanded .Row.v { + display: block; } + .Box.mobcol #ap-box { + display: none; } + .Box.mobcol.expanded #ap-box { + display: block; } } +.Modal { + z-index: 100; + position: fixed; + width: 100%; + height: 100%; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + transition: opacity .5s; + background: rgba(0, 0, 0, 0.65); + opacity: 0; } + .Modal.visible { + opacity: 1; } + .Modal.hidden { + display: none; } + .Modal.light { + background: rgba(0, 0, 0, 0.25); } + @media screen and (max-width: 544px) { + .Modal { + flex-direction: column; + justify-content: flex-start; + overflow-y: auto; } + .Modal .Dialog { + margin-top: 0.61805rem !important; + flex-basis: unset; + flex-shrink: 0; } } + +.Dialog { + margin: 0.61805rem; + padding: 1rem; + overflow: hidden; + flex-basis: 35rem; + background: #242426; + border-left: 6px solid #2972ba; + border-right: 6px solid #2972ba; + box-shadow: 0 0 6px 0 black; + border-radius: 6px; } + .Dialog h1, .Dialog h2 { + margin-top: 0; } + .Dialog p:last-child { + margin-bottom: 0; } + +.NotifyMsg { + position: fixed; + top: 1.618rem; + right: 2.61792rem; + padding: 0.61805rem 1rem; + -webkit-font-smoothing: subpixel-antialiased; + -webkit-transform: translateZ(0) scale(1, 1); + background: #3887d0; + color: white; + text-shadow: 0 0 2px black; + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.6); + border-radius: 5px; + max-width: 80%; + transition: opacity .5s; + opacity: 0; } + .NotifyMsg.error { + background: #d03e42; } + @media screen and (max-width: 544px) { + .NotifyMsg { + width: calc(100% - 1rem); } } + .NotifyMsg.visible { + opacity: 1; } + .NotifyMsg.hidden { + display: none; } + +button, input[type=submit], .button { + text-align: center; + cursor: pointer; + display: inline-block; + border-radius: 2px; + padding: 0 0.6em; + border: 0 none; + line-height: 1.8em; + font-size: 1.1em; + margin-bottom: 3px; + min-width: 5em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-shadow: 1.5px 1.5px 2px rgba(0, 0, 0, 0.4); + background-color: #3983cd; + box-shadow: 0 3px 0 #265f98; + text-decoration: none !important; } + button:active, input[type=submit]:active, .button:active { + position: relative; + top: 2px; } + button.narrow, input[type=submit].narrow, .button.narrow { + min-width: initial; } + button::before, input[type=submit]::before, .button::before { + vertical-align: -1px; + margin-left: 0; } + button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited, .button, .button:link, .button:visited { + color: #FEFEFE; } + button:hover, button:active, button.active, button.selected, input[type=submit]:hover, input[type=submit]:active, input[type=submit].active, input[type=submit].selected, .button:hover, .button:active, .button.active, .button.selected { + background-color: #2076C6; + color: #FEFEFE; } + button:hover, button.selected, button.active, input[type=submit]:hover, input[type=submit].selected, input[type=submit].active, .button:hover, .button.selected, .button.active { + box-shadow: 0 3px 0 #154c80; } + button:active, input[type=submit]:active, .button:active { + box-shadow: 0 1px 0 #154c80; } + button:focus, input[type=submit]:focus, .button:focus { + outline-color: #ff0099; } + +button, input[type=submit], .button { + text-align: center; + cursor: pointer; + display: inline-block; + border-radius: 2px; + padding: 0 0.6em; + border: 0 none; + line-height: 1.8em; + font-size: 1.1em; + margin-bottom: 3px; + min-width: 5em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-shadow: 1.5px 1.5px 2px rgba(0, 0, 0, 0.4); + background-color: #3983cd; + box-shadow: 0 3px 0 #265f98; + text-decoration: none !important; } + button:active, input[type=submit]:active, .button:active { + position: relative; + top: 2px; } + button.narrow, input[type=submit].narrow, .button.narrow { + min-width: initial; } + button::before, input[type=submit]::before, .button::before { + vertical-align: -1px; + margin-left: 0; } + button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited, .button, .button:link, .button:visited { + color: #FEFEFE; } + button:hover, button:active, button.active, button.selected, input[type=submit]:hover, input[type=submit]:active, input[type=submit].active, input[type=submit].selected, .button:hover, .button:active, .button.active, .button.selected { + background-color: #2076C6; + color: #FEFEFE; } + button:hover, button.selected, button.active, input[type=submit]:hover, input[type=submit].selected, input[type=submit].active, .button:hover, .button.selected, .button.active { + box-shadow: 0 3px 0 #154c80; } + button:active, input[type=submit]:active, .button:active { + box-shadow: 0 1px 0 #154c80; } + button:focus, input[type=submit]:focus, .button:focus { + outline-color: #ff0099; } + +input[type="number"], input[type="password"], input[type="text"], textarea, select, label.select-wrap { + width: 250px; } + +input[type="number"], input.short, select.short { + width: 125px; } + +input[type="number"], input[type="password"], input[type="text"], textarea, select { + border: 0 none; + border-bottom: 2px solid #2972ba; + background-color: #3c3c3c; + color: white; + padding: 6px; + line-height: 1em; + font-weight: normal; } + input[type="number"]:focus, input[type="number"]:hover, input[type="password"]:focus, input[type="password"]:hover, input[type="text"]:focus, input[type="text"]:hover, textarea:focus, textarea:hover, select:focus, select:hover { + border-bottom-color: #2ea1f9; } + +.Row.checkbox { + line-height: 27px; } + .Row.checkbox .box { + overflow: hidden; + width: 27px; + height: 27px; + border: 1px solid #808080; + border-radius: 3px; + background: #3c3c3c; + display: inline-block; + position: relative; + cursor: pointer; + color: #2ea1f9; } + .Row.checkbox .box::before { + font-family: "fontello"; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + line-height: 25px; + text-align: center; + font-size: 20px; + vertical-align: middle; + display: none; } + .Row.checkbox .box::before { + content: "✔"; } + .Row.checkbox .box.checked::before { + display: block; } + +.Row.range .display { + margin-left: 1ex; } +.Row.range label .display { + font-weight: normal; } + +#psk-modal form > *, #wificonfbox form > * { + margin-right: 0.38198rem; } + #psk-modal form > *:last-child, #wificonfbox form > *:last-child { + margin-right: 0; } + +form { + border: 0 none; + margin: 0; + padding: 0; + text-decoration: none; } + +.Box.errors .list { + color: crimson; + font-weight: bold; } +.Box.errors .lead { + color: white; } + +.Row { + vertical-align: middle; + margin: 12px auto; + text-align: left; + line-height: 1.35em; + display: flex; + flex-direction: row; + align-items: center; } + .Row:first-child { + margin-top: 0; } + .Row:last-child { + margin-bottom: 0; } + .Row.v { + display: block; } + .Row .aside { + float: right; + margin-left: 5px; + margin-bottom: 5px; } + @media screen and (max-width: 544px) { + .Row .aside { + margin: 0; + float: none; } } + .Row .spacer { + width: 160px; } + @media screen and (max-width: 544px) { + .Row .spacer { + display: none; } } + .Row.buttons, .Row.buttons2 { + margin: 16px auto; } + .Row.buttons input, .Row.buttons .button, .Row.buttons2 input, .Row.buttons2 .button { + margin-right: 0.61805rem; } + .Row.centered { + justify-content: center; } + .Row.message { + font-size: 1em; + text-shadow: 1px 1px 3px black; + text-align: center; } + .Row.message.error { + color: crimson; } + .Row.message.ok { + color: #0fe851; } + .Row.separator { + padding-top: 14px; + border-top: 2px solid rgba(255, 255, 255, 0.1); } + .Row textarea { + display: inline-block; + vertical-align: top; + min-height: 10rem; + flex-grow: 1; + resize: vertical; } + .Row label { + font-weight: bold; + color: white; + display: inline-block; + width: 160px; + text-align: right; + text-shadow: 1px 1px 3px black; + padding: 8px; + align-self: flex-start; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + white-space: nowrap; + word-wrap: normal; } + .Row label.error { + color: crimson; } + .Row input[type="range"] { + width: 200px; } + @media screen and (max-width: 544px) { + .Row { + flex-direction: column; + margin: 6px auto; } + .Row.buttons, .Row.centered, .Row.checkbox { + flex-direction: row; } + .Row.buttons { + justify-content: center; } + .Row.buttons :last-child { + margin-right: 0; } + .Row label { + padding-left: 0; + text-align: left; + width: auto; } + .Row .checkbox-wrap { + order: 1; + text-align: left; + padding-bottom: 0; + border-radius: .4px; + width: auto; } + .Row .checkbox-wrap + label { + width: auto; } + .Row input[type="number"], .Row input[type="password"], .Row input[type="text"], .Row textarea, .Row input[type="range"], .Row textarea, .Row select { + width: 100%; } } + +form span.required { + color: red; } + +.RadioGroup { + display: inline-block; + line-height: 1.5em; + vertical-align: middle; } + .RadioGroup label { + width: auto; + text-align: left; + cursor: pointer; + font-weight: normal; } + .RadioGroup input[type="radio"] { + vertical-align: middle; + margin: 0 0 0 5px; } + +#ap-list { + column-count: 3; + column-gap: 0; + margin: 0 -0.23608rem; } + @media screen and (min-width: 545px) and (max-width: 1000px) { + #ap-list { + column-count: 2; } } + @media screen and (max-width: 544px) { + #ap-list { + column-count: 1; } } + +#ap-loader, #ap-noscan, #ap-scan { + background: rgba(255, 255, 255, 0.1); + border-radius: 5px; + padding: 0.38198rem; + margin-bottom: 0.38198rem; + margin-top: 0.38198rem; + font-size: 110%; } + +#ap-noscan { + font-weight: bold; } + +#ap-box { + padding-top: 0.38198rem; } + #ap-box label { + display: block; + color: white; + font-weight: bold; + margin-bottom: 0.23608rem; } + +#psk-modal form { + display: flex; + align-items: center; + margin: 0.38198rem; } + #psk-modal form input[type=password] { + min-width: 5rem; } + +.AP .inner, .AP-preview .wrap { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + border-radius: 3px; + color: #222; + background: #afafaf; + transition: background-color 0.5s; + display: flex; } + .AP .inner:active, .AP-preview .wrap:active { + left: 0; + top: 1px; } + .AP .inner:hover, .AP-preview .wrap:hover { + background: white; } + .AP .inner .rssi, .AP-preview .wrap .rssi { + min-width: 2.5rem; + flex: 0 0 15%; + text-align: right; } + .AP .inner .rssi:after, .AP-preview .wrap .rssi:after { + padding-left: 0.09018rem; + content: '%'; + font-size: 0.88889em; } + .AP .inner .essid, .AP-preview .wrap .essid { + flex: 1 1 70%; + min-width: 0; + text-overflow: ellipsis; + overflow: hidden; + font-weight: bold; } + .AP .inner .auth, .AP-preview .wrap .auth { + flex: 0 0 15%; } + +.AP { + break-inside: avoid-column; + max-width: 500px; + padding: 0.23608rem; } + .AP.selected .inner { + background: #42a6f9 !important; + cursor: default; + top: 0 !important; } + .AP .inner > * { + padding: 0.61805rem; + white-space: nowrap; + word-wrap: normal; } + +.AP-preview-nil { + padding: 8px; + border-radius: 5px; + border: 1px dashed #ddd; + width: 250px; + height: 94px; } + +.AP-preview .wrap { + flex-direction: row; + background: #ddd !important; + cursor: default; + top: 0 !important; + overflow: hidden; } + .AP-preview .wrap .inner { + display: flex; + flex-direction: column; } + .AP-preview .wrap .inner > * { + padding: 0.61805rem; + white-space: nowrap; + word-wrap: normal; } + .AP-preview .wrap .forget { + align-self: stretch; + line-height: 100%; + padding: 0.61805rem; + border-left: 1px solid #bbb; + display: flex; + align-items: center; + font-size: 28px; } + .AP-preview .wrap .forget, .AP-preview .wrap .forget:hover { + color: black; + text-decoration: none; } + .AP-preview .wrap .forget:hover { + background: #dc4a6a; + color: white; + border-left: 1px solid #666; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; } + .AP-preview .wrap .forget:active { + position: relative; + padding-top: calc(0.61805rem + 1px); } + .AP-preview .wrap .essid, .AP-preview .wrap .passwd, .AP-preview .wrap .nopasswd { + padding-bottom: 0; } + .AP-preview .wrap .x-passwd { + font-family: monospace; } + +body.term #content { + padding-left: 0; + padding-right: 0; + transition: opacity 0.25s ease-in; } + body.term #content h1 { + font-size: 1.80203em; } + @media screen and (max-width: 544px) { + body.term #content h1 { + font-size: 1.42383em; } } + +#screen { + white-space: nowrap; + background: #111213; + padding: 6px; + display: inline-block; + border: 2px solid #3983CD; + font-size: 20px; + font-family: "DejaVu Sans Mono", "Liberation Mono", "Inconsolata", monospace; } + #screen span { + white-space: pre; } + #screen > span { + position: relative; + cursor: pointer; } + #screen > span::before { + content: " "; } + #screen > span > span { + position: absolute; + left: 0; + z-index: 1; } + #screen.noselect { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +#action-buttons { + margin-top: 10px; + white-space: nowrap; } + #action-buttons button { + margin: 0 3px; + padding: 8px 5px; + min-width: 62px; + cursor: pointer; + font-weight: bold; } + #action-buttons button:focus { + outline: 0 none !important; } + +#term-nav { + padding-top: 1.5em; + text-align: center; } + #term-nav a { + text-decoration: none; + padding: 5px 5px; + border-radius: 2px; + position: relative; } + #term-nav a, #term-nav a:visited, #term-nav a:link { + color: #336085; } + #term-nav a:hover { + color: #5abfff; + background: #1b273c; } + #term-nav a:active { + top: 1px; } + #term-nav a i::before { + display: inline; } + #term-nav a span { + margin-left: .2em; } + @media screen and (max-width: 544px) { + #term-nav a { + font-size: 130%; + padding: 6px; } + #term-nav a span { + display: none; } } + #term-nav .icn-keyboard { + text-decoration: none; + font-size: 130%; + vertical-align: middle; } + +#term-wrap { + text-align: center; } + +#softkb-input { + position: absolute; + top: -9999px; } + +#fu_modal { + align-items: flex-start; } + +#fu_form { + padding: 1rem; + margin-top: 100px; + z-index: 1000; } + #fu_form label { + width: 8em; + display: inline-block; } + #fu_form input[type="number"], #fu_form select { + width: 10em; } + #fu_form textarea { + width: 100%; + min-height: 8em; + margin-top: 0.61805rem; + resize: vertical; } + #fu_form .fu-buttons { + text-align: center; + padding: 0.38198rem; + margin-top: 0.38198rem; + border-radius: 3px; } + #fu_form .fu-prog-box { + display: none; } + #fu_form.busy { + background: rgba(36, 36, 38, 0.3); + border-left-color: rgba(41, 114, 186, 0.3); + border-right-color: rgba(41, 114, 186, 0.3); } + #fu_form.busy .fu-content { + pointer-events: none; + opacity: .3; } + #fu_form.busy .fu-buttons { + text-align: left; + background: #242426; + border: 1px solid #2972ba; } + #fu_form.busy .fu-prog-box { + display: inline-block; } + +.theme-0 .fg0 { + color: #111213; } +.theme-0 .bg0 { + background-color: #111213; } +.theme-0 .fg1 { + color: #CC0000; } +.theme-0 .bg1 { + background-color: #CC0000; } +.theme-0 .fg2 { + color: #4E9A06; } +.theme-0 .bg2 { + background-color: #4E9A06; } +.theme-0 .fg3 { + color: #C4A000; } +.theme-0 .bg3 { + background-color: #C4A000; } +.theme-0 .fg4 { + color: #3465A4; } +.theme-0 .bg4 { + background-color: #3465A4; } +.theme-0 .fg5 { + color: #75507B; } +.theme-0 .bg5 { + background-color: #75507B; } +.theme-0 .fg6 { + color: #06989A; } +.theme-0 .bg6 { + background-color: #06989A; } +.theme-0 .fg7 { + color: #D3D7CF; } +.theme-0 .bg7 { + background-color: #D3D7CF; } +.theme-0 .fg8 { + color: #555753; } +.theme-0 .bg8 { + background-color: #555753; } +.theme-0 .fg9 { + color: #EF2929; } +.theme-0 .bg9 { + background-color: #EF2929; } +.theme-0 .fg10 { + color: #8AE234; } +.theme-0 .bg10 { + background-color: #8AE234; } +.theme-0 .fg11 { + color: #FCE94F; } +.theme-0 .bg11 { + background-color: #FCE94F; } +.theme-0 .fg12 { + color: #729FCF; } +.theme-0 .bg12 { + background-color: #729FCF; } +.theme-0 .fg13 { + color: #AD7FA8; } +.theme-0 .bg13 { + background-color: #AD7FA8; } +.theme-0 .fg14 { + color: #34E2E2; } +.theme-0 .bg14 { + background-color: #34E2E2; } +.theme-0 .fg15 { + color: #EEEEEC; } +.theme-0 .bg15 { + background-color: #EEEEEC; } + +.theme-1 .fg0 { + color: #000000; } +.theme-1 .bg0 { + background-color: #000000; } +.theme-1 .fg1 { + color: #aa0000; } +.theme-1 .bg1 { + background-color: #aa0000; } +.theme-1 .fg2 { + color: #00aa00; } +.theme-1 .bg2 { + background-color: #00aa00; } +.theme-1 .fg3 { + color: #aa5500; } +.theme-1 .bg3 { + background-color: #aa5500; } +.theme-1 .fg4 { + color: #0000aa; } +.theme-1 .bg4 { + background-color: #0000aa; } +.theme-1 .fg5 { + color: #aa00aa; } +.theme-1 .bg5 { + background-color: #aa00aa; } +.theme-1 .fg6 { + color: #00aaaa; } +.theme-1 .bg6 { + background-color: #00aaaa; } +.theme-1 .fg7 { + color: #aaaaaa; } +.theme-1 .bg7 { + background-color: #aaaaaa; } +.theme-1 .fg8 { + color: #555555; } +.theme-1 .bg8 { + background-color: #555555; } +.theme-1 .fg9 { + color: #ff5555; } +.theme-1 .bg9 { + background-color: #ff5555; } +.theme-1 .fg10 { + color: #55ff55; } +.theme-1 .bg10 { + background-color: #55ff55; } +.theme-1 .fg11 { + color: #ffff55; } +.theme-1 .bg11 { + background-color: #ffff55; } +.theme-1 .fg12 { + color: #5555ff; } +.theme-1 .bg12 { + background-color: #5555ff; } +.theme-1 .fg13 { + color: #ff55ff; } +.theme-1 .bg13 { + background-color: #ff55ff; } +.theme-1 .fg14 { + color: #55ffff; } +.theme-1 .bg14 { + background-color: #55ffff; } +.theme-1 .fg15 { + color: #ffffff; } +.theme-1 .bg15 { + background-color: #ffffff; } + +.theme-2 .fg0 { + color: #000000; } +.theme-2 .bg0 { + background-color: #000000; } +.theme-2 .fg1 { + color: #cd0000; } +.theme-2 .bg1 { + background-color: #cd0000; } +.theme-2 .fg2 { + color: #00cd00; } +.theme-2 .bg2 { + background-color: #00cd00; } +.theme-2 .fg3 { + color: #cdcd00; } +.theme-2 .bg3 { + background-color: #cdcd00; } +.theme-2 .fg4 { + color: #0000ee; } +.theme-2 .bg4 { + background-color: #0000ee; } +.theme-2 .fg5 { + color: #cd00cd; } +.theme-2 .bg5 { + background-color: #cd00cd; } +.theme-2 .fg6 { + color: #00cdcd; } +.theme-2 .bg6 { + background-color: #00cdcd; } +.theme-2 .fg7 { + color: #e5e5e5; } +.theme-2 .bg7 { + background-color: #e5e5e5; } +.theme-2 .fg8 { + color: #7f7f7f; } +.theme-2 .bg8 { + background-color: #7f7f7f; } +.theme-2 .fg9 { + color: #ff0000; } +.theme-2 .bg9 { + background-color: #ff0000; } +.theme-2 .fg10 { + color: #00ff00; } +.theme-2 .bg10 { + background-color: #00ff00; } +.theme-2 .fg11 { + color: #ffff00; } +.theme-2 .bg11 { + background-color: #ffff00; } +.theme-2 .fg12 { + color: #5c5cff; } +.theme-2 .bg12 { + background-color: #5c5cff; } +.theme-2 .fg13 { + color: #ff00ff; } +.theme-2 .bg13 { + background-color: #ff00ff; } +.theme-2 .fg14 { + color: #00ffff; } +.theme-2 .bg14 { + background-color: #00ffff; } +.theme-2 .fg15 { + color: #ffffff; } +.theme-2 .bg15 { + background-color: #ffffff; } + +.theme-3 .fg0 { + color: #000000; } +.theme-3 .bg0 { + background-color: #000000; } +.theme-3 .fg1 { + color: #cd0000; } +.theme-3 .bg1 { + background-color: #cd0000; } +.theme-3 .fg2 { + color: #00cd00; } +.theme-3 .bg2 { + background-color: #00cd00; } +.theme-3 .fg3 { + color: #cdcd00; } +.theme-3 .bg3 { + background-color: #cdcd00; } +.theme-3 .fg4 { + color: #0000cd; } +.theme-3 .bg4 { + background-color: #0000cd; } +.theme-3 .fg5 { + color: #cd00cd; } +.theme-3 .bg5 { + background-color: #cd00cd; } +.theme-3 .fg6 { + color: #00cdcd; } +.theme-3 .bg6 { + background-color: #00cdcd; } +.theme-3 .fg7 { + color: #faebd7; } +.theme-3 .bg7 { + background-color: #faebd7; } +.theme-3 .fg8 { + color: #404040; } +.theme-3 .bg8 { + background-color: #404040; } +.theme-3 .fg9 { + color: #ff0000; } +.theme-3 .bg9 { + background-color: #ff0000; } +.theme-3 .fg10 { + color: #00ff00; } +.theme-3 .bg10 { + background-color: #00ff00; } +.theme-3 .fg11 { + color: #ffff00; } +.theme-3 .bg11 { + background-color: #ffff00; } +.theme-3 .fg12 { + color: #0000ff; } +.theme-3 .bg12 { + background-color: #0000ff; } +.theme-3 .fg13 { + color: #ff00ff; } +.theme-3 .bg13 { + background-color: #ff00ff; } +.theme-3 .fg14 { + color: #00ffff; } +.theme-3 .bg14 { + background-color: #00ffff; } +.theme-3 .fg15 { + color: #ffffff; } +.theme-3 .bg15 { + background-color: #ffffff; } + +.theme-4 .fg0 { + color: #2e3436; } +.theme-4 .bg0 { + background-color: #2e3436; } +.theme-4 .fg1 { + color: #cc0000; } +.theme-4 .bg1 { + background-color: #cc0000; } +.theme-4 .fg2 { + color: #4e9a06; } +.theme-4 .bg2 { + background-color: #4e9a06; } +.theme-4 .fg3 { + color: #c4a000; } +.theme-4 .bg3 { + background-color: #c4a000; } +.theme-4 .fg4 { + color: #3465a4; } +.theme-4 .bg4 { + background-color: #3465a4; } +.theme-4 .fg5 { + color: #75507b; } +.theme-4 .bg5 { + background-color: #75507b; } +.theme-4 .fg6 { + color: #06989a; } +.theme-4 .bg6 { + background-color: #06989a; } +.theme-4 .fg7 { + color: #d3d7cf; } +.theme-4 .bg7 { + background-color: #d3d7cf; } +.theme-4 .fg8 { + color: #555753; } +.theme-4 .bg8 { + background-color: #555753; } +.theme-4 .fg9 { + color: #ef2929; } +.theme-4 .bg9 { + background-color: #ef2929; } +.theme-4 .fg10 { + color: #8ae234; } +.theme-4 .bg10 { + background-color: #8ae234; } +.theme-4 .fg11 { + color: #fce94f; } +.theme-4 .bg11 { + background-color: #fce94f; } +.theme-4 .fg12 { + color: #729fcf; } +.theme-4 .bg12 { + background-color: #729fcf; } +.theme-4 .fg13 { + color: #ad7fa8; } +.theme-4 .bg13 { + background-color: #ad7fa8; } +.theme-4 .fg14 { + color: #34e2e2; } +.theme-4 .bg14 { + background-color: #34e2e2; } +.theme-4 .fg15 { + color: #eeeeec; } +.theme-4 .bg15 { + background-color: #eeeeec; } + +.theme-5 .fg0 { + color: #073642; } +.theme-5 .bg0 { + background-color: #073642; } +.theme-5 .fg1 { + color: #dc322f; } +.theme-5 .bg1 { + background-color: #dc322f; } +.theme-5 .fg2 { + color: #859900; } +.theme-5 .bg2 { + background-color: #859900; } +.theme-5 .fg3 { + color: #b58900; } +.theme-5 .bg3 { + background-color: #b58900; } +.theme-5 .fg4 { + color: #268bd2; } +.theme-5 .bg4 { + background-color: #268bd2; } +.theme-5 .fg5 { + color: #d33682; } +.theme-5 .bg5 { + background-color: #d33682; } +.theme-5 .fg6 { + color: #2aa198; } +.theme-5 .bg6 { + background-color: #2aa198; } +.theme-5 .fg7 { + color: #eee8d5; } +.theme-5 .bg7 { + background-color: #eee8d5; } +.theme-5 .fg8 { + color: #002b36; } +.theme-5 .bg8 { + background-color: #002b36; } +.theme-5 .fg9 { + color: #cb4b16; } +.theme-5 .bg9 { + background-color: #cb4b16; } +.theme-5 .fg10 { + color: #586e75; } +.theme-5 .bg10 { + background-color: #586e75; } +.theme-5 .fg11 { + color: #657b83; } +.theme-5 .bg11 { + background-color: #657b83; } +.theme-5 .fg12 { + color: #839496; } +.theme-5 .bg12 { + background-color: #839496; } +.theme-5 .fg13 { + color: #6c71c4; } +.theme-5 .bg13 { + background-color: #6c71c4; } +.theme-5 .fg14 { + color: #93a1a1; } +.theme-5 .bg14 { + background-color: #93a1a1; } +.theme-5 .fg15 { + color: #fdf6e3; } +.theme-5 .bg15 { + background-color: #fdf6e3; } + +.bold { + font-weight: bold !important; } + +.faint span { + opacity: 0.6; } + +.italic { + font-style: italic; } + +.under { + text-decoration: underline; } + +.strike { + text-decoration: line-through; } + +.underline.strike { + text-decoration: underline line-through; } + +.blink-hide .blink { + color: transparent; } + +.Row.color-preview { + font-family: monospace; + font-size: 16pt; + display: block; + margin-bottom: 0; + padding-left: 160px; } + @media screen and (max-width: 544px) { + .Row.color-preview { + padding-left: 0; + font-size: 14pt; } } + .Row.color-preview .colorprev { + display: block; + margin: 0; + cursor: pointer; } + +#color-example { + display: inline-block; + padding: 5px; } + +.page-about .Box { + padding-left: 1rem; + padding-right: 1rem; } + .page-about .Box a { + font-weight: bold; } +.page-about #logo { + float: right; + height: 130px; } +.page-about #logo2 { + max-width: 100%; + margin: 1rem; } +.page-about td { + white-space: normal; } + +.page-help code { + background: rgba(33, 97, 109, 0.31); + border-radius: 1px; + padding: 0 2px; } + +.colorprev { + margin-top: 0.38198rem; + margin-bottom: 0.38198rem; } + .colorprev span { + display: inline-block; + width: 2em; + padding: 0.38198rem 0; + text-align: center; } + +.Row table, .Row table td, .Row table th { + border: 1px solid #666; } +.Row table th, .Row table td { + white-space: normal; } +.Row table th { + background-color: rgba(255, 255, 255, 0.1); } + +.ansiref.w100 { + width: 100%; } + .ansiref.w100 td:nth-child(1) { + width: 8em; } + +.nomen { + width: 100%; } + .nomen code { + white-space: nowrap; } + .nomen td:last-child { + min-width: 15em; } + +.tscroll { + overflow-x: auto; } + +.charset { + line-height: 1; } + .charset div { + display: inline-block; + width: 2.7em; + border: 1px solid #666; + height: 3em; + margin: 1px; + position: relative; } + .charset div span { + display: block; + position: absolute; } + .charset div span:nth-child(1) { + left: .2em; + top: .2em; + height: 1em; + font-size: 85%; + color: #999; } + .charset div span:nth-child(2) { + right: .2em; + top: .2em; + height: 1em; + font-size: 85%; + color: #999; } + .charset div span:nth-child(3) { + width: 100%; + font-size: 105%; + text-align: center; + bottom: .4em; + font-family: "DejaVu Sans Mono", "Liberation Mono", "Inconsolata", monospace; } + .charset div.none { + opacity: .4; } + +@media screen and (min-width: 545px) { + .mq-phone { + display: none !important; } } +@media screen and (max-width: 544px) { + .mq-tablet-min, .mq-no-phone { + display: none !important; } } +@media screen and (min-width: 1001px) { + .mq-tablet-max { + display: none !important; } } +@media screen and (max-width: 1000px) { + .mq-normal-min { + display: none !important; } } + +/*# sourceMappingURL=app.css.map */ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6916039458a6efbad8b98f6820c2eee94f7e0972 GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wf0ls%IcHxuhUEU=$Pt hngt}F5&}R0Fu)}MQvj7m;|jvGLrp=a$aObN9{@zp3F!a; literal 0 HcmV?d00001 diff --git a/help.html b/help.html new file mode 100644 index 0000000..a1fa80e --- /dev/null +++ b/help.html @@ -0,0 +1,959 @@ + + + + + + + Quick Reference :: ESPTerm + + + + + +
+ + + + +
+Loading… +

Quick Reference

+ + + + + +
+

Tips & Troubleshooting

+ +
+
    +
  • Communication UART (Rx, Tx) can be configured in the System Settings. + +
  • Boot log and debug messages are available on pin GPIO2 (P2) at 115200 baud, 1 stop bit, no parity. + Those messages may be disabled through compile flags. + +
  • Loopback test: Connect the Rx and Tx pins with a piece of wire. Anything you type in the browser should + appear on the screen. Set Parser Timeout = 0 in Terminal Settings + to be able to manually enter escape sequences. + +
  • There is very little RAM available to the webserver, and it can support at most 4 connections at the same time. + Each terminal session (open window with the terminal screen) uses one persistent connection for screen updates. + Avoid leaving unused windows open, or either the RAM or connections may be exhausted. + +
  • For best performance, use the module in Client mode (connected to external network) and minimize the number + of simultaneous connections. Enabling AP consumes extra RAM because the DHCP server and Captive Portal + DNS server are started. + +
  • In AP mode, check that the WiFi channel used is clear; interference may cause flaky connection. + A good mobile app to use for this is + WiFi Analyzer (Google Play). + Adjust the hotspot strength and range using the Tx Power setting. + +
  • Hold the BOOT button (GPIO0 to GND) for ~1 second to force enable AP. Hold it for ~6 seconds to restore default settings. + (This is indicated by the blue LED rapidly flashing). Default settings can be overwritten in the + System Settings. +
+
+
+ +
+

Basic Intro & Nomenclature

+ +
+ VT102 + +

+ ESPTerm emulates VT102 (pictured) with some additions from later VT models and Xterm. + All commonly used attributes and commands are supported. + ESPTerm is capable of displaying ncurses applications such as Midnight Commander using agetty. +

+ +

+ ESPTerm accepts UTF-8 characters received on the communication UART and displays them on the screen, + interpreting some codes as Control Characters. Those are e.g. Carriage Return (13), Line Feed (10), + Tab (9), Backspace (8) and Bell (7). +

+ +

+ Escape sequences start with the control character ESC (27), + followed by any number of ASCII characters forming the body of the command. +

+ +

Nomenclature & Command Types

+ +

+ Examples on this help page use the following symbols for special characters and command types:
(spaces are for clarity only, DO NOT include them in the commands!) +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSymbolASCIIC stringFunction
ESC\eESC (27)"\e", "\x1b", "\033"Introduces an escape sequence. (Note: \e is a GCC extension)
Bell\aBEL (7)"\a", "\x7", "\07"Audible beep
String TerminatorSTESC \ (27 92)
or \a (7)
"\x1b\\", "\a"Terminates a string command (\a can be used as an alternative)
Control Sequence IntroducerCSIESC ["\x1b["Starts a CSI command. Examples: \e[?7;10h, \e[2J
Operating System CommandOSCESC ]"\x1b]"Starts an OSC command. Is followed by a command string terminated by ST. Example: \e]0;My Screen Title\a
Select Graphic RenditionSGRCSI n;n;nm"\x1b[1;2;3m"Set text attributes, like color or style. 0 to 10 numbers can be used, \e[m is treated as \e[0m
+
+ +

There are also some other commands that don't follow the CSI, SGR or OSC pattern, such as \e7 or + \e#8. A list of the most important escape sequences is presented in the following sections.

+
+
+ +
+

Screen Behavior & Refreshing

+ +
+

+ The initial screen size, title text and button labels can be configured in Terminal Settings. +

+ +

+ Screen updates are sent to the browser through a WebSocket after some time of inactivity on the communication UART + (called "Redraw Delay"). After an update is sent, at least a time of "Redraw Cooldown" must elapse before the next + update can be sent. Those delays are used is to avoid burdening the server with tiny updates during a large screen + repaint. If you experience issues (broken image due to dropped bytes), try adjusting those config options. It may also + be useful to try different baud rates. +

+
+
+ + +
+

User Input: Keyboard, Mouse

+ +
+

Keyboard

+ +

+ The user can input text using their keyboard, or on Android, using the on-screen keyboard which is open using + a button beneath the screen. Supported are all printable characters, as well as many control keys, such as arrows, Ctrl+letters + and function keys. Sequences sent by function keys are based on VT102 and Xterm. +

+ +

+ The codes sent by Home, End, F1-F4 and cursor keys are affected by various keyboard modes (Application Cursor Keys, + Application Numpad Mode, SS3 Fn Keys Mode). Some can be set in the Terminal Settings, + others via commands. +

+ +

+ Here are some examples of control key codes: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyCodeKeyCode
Up\e[A\eOAF1\eOP\e[11~
Down\e[B\eOBF2\eOQ\e[12~
Right\e[C\eOCF3\eOR\e[13~
Left\e[D\eODF4\eOS\e[14~
Home\eOH\e[H\e[1~F5\e[15 
End\eOF\e[F\e[4~F6\e[17~
Insert\e[2~F7\e[18~
Delete\e[3~F8\e[19~
Page Up\e[5~F9\e[20~
Page Down\e[6~F10\e[21~
Enter\r (13)F11\e[23~
Ctrl+Enter\n (10)F12\e[24~
Tab\t (9)ESC\e (27)
Backspace\b (8)Ctrl+A..ZASCII 1-26
+ +

Action buttons

+ +

+ The blue buttons under the screen send ASCII codes 1, 2, 3, 4, 5, which incidentally + correspond to Ctrl+A,B,C,D,E. This choice was made to make button press parsing as simple as possible. +

+ +

Mouse

+ +

+ ESPTerm implements standard mouse tracking modes based on Xterm. Mouse tracking can be used to implement + powerful user interactions such as on-screen buttons, draggable sliders or dials, menus etc. ESPTerm's + mouse tracking was tested using VTTest and should be compatible with all terminal applications + that request mouse tracking. +

+ +

+ Mouse can be tracked in different ways; some are easier to parse, others more powerful. The coordinates + can also be encoded in different ways. All mouse tracking options are set using option commands: + CSI ? n h to enable, CSI ? n l to disable option n. +

+ +

Mouse Tracking Modes

+ +

+ All tracking modes produce three numbers which are then encoded and send to the application. + First is the event number N, then the X and Y coordinates, 1-based. +

+ +

+ Mouse buttons are numbered: 1=left, 2=middle, 3=right. + Wheel works as two buttons (4 and 5) which generate only press events (no release). +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionNameDescription
9X10 mode + This is the most basic tracking mode, in which only button presses are reported. + N = button - 1: (0 left, 1 middle, 2 right, 3, 4 wheel). +
1000Normal mode + In Normal mode, both button presses and releases are reported. + The lower two bits of N indicate the button pressed: + 00b (0) left, 01b (1) middle, 10b (2) right, 11b (3) button release. + Wheel buttons are reported as 0 and 1 with added 64 (e.g. 64 and 65). + Normal mode also supports tracking of modifier keys, which are added to N as bit masks: + 4=Shift, 8=Meta/Alt, 16=Control/Cmd. Example: middle button with Shift = 1 + 4 = 101b (5). +
1002Button-Event tracking + This is similar to Normal mode (1000), but mouse motion with a button held is also reported. + A motion event is generated when the mouse cursor moves between screen character cells. + A motion event has the same N as a press event, but 32 is added. + For example, drag-drop event with the middle button will produce N = 1 (press), 33 (dragging) and 3 (release). +
1003Any-Event tracking + This mode is almost identical to Button Event tracking (1002), but motion events + are sent even when no mouse buttons are held. This could be used to draw on-screen mouse cursor, for example. + Motion events with no buttons will use N = 32 + 11b (35). +
1004Focus tracking + Focus tracking is a separate function from the other mouse tracking modes, therefore they can be enabled together. + Focus tracking reports when the terminal window (in Xterm) gets or loses focus, or in ESPTerm's case, when any + user is connected. This can be used to pause/resume a game or on-screen animations. + Focus tracking mode sends CSI I when the terminal receives, and CSI O when it loses focus. +
+
+ +

Mouse Report Encoding

+ +

+ The following encoding schemes can be used with any of the tracking modes (except Focus tracking, which is not affected). +

+ +
+ + + + + + + + + + + + + + + + + + + + + + +
OptionNameDescription
--Normal encoding + This is the default encoding scheme used when no other option is selected. + In this mode, a mouse report has the format CSI M n x y, + where n, x and y are characters with ASCII value = 32 (space) + the respective number, e.g. + 0 becomes 32 (space), 1 becomes 33 (!). The reason for adding 32 is to avoid producing control characters. + Example: \e[M !! - left button press at coordinates 1,1 when using X10 mode. +
1005UTF-8 encoding + This scheme should encode each of the numbers as a UTF-8 code point, expanding the maximum possible value. + Since ESPTerm's screen size is limited and this has no practical benefit, this serves simply as an alias + to the normal scheme. +
1006SGR encoding + In SGR encoding, the response looks like a SGR sequence with the three numbers as semicolon-separated + ASCII values. In this case 32 is not added like in the Normal and UTF-8 schemes, because + it would serve nor purpose here. Also, button release is not reported as 11b, + but using the normal button code while changing the final SGR character: M for button press + and m for button release. Example: \e[2;80;24m - the right button was released + at row 80, column 24. +
1015URXVT encoding + This is similar to SGR encoding, but the final character is always M and the numbers are + like in the Normal scheme, with 32 added. This scheme has no real advantage over the previous schemes and + was added solely for completeness. +
+
+
+
+ +
+

Alternate Character Sets

+ +
+

+ ESPTerm implements Alternate Character Sets as a way to print box drawing characters + and special symbols. A character set can change what each received ASCII character + is printed as on the screen (eg. "{" is "π" in codepage 0). The implementation is based + on the original VT devices. +

+ +

+ Since ESPTerm also supports UTF-8, this feature is the most useful for applications + which can't print UTF-8 or already use alternate character sets for historical reasons. +

+ +

Supported codepages

+ +
    +
  • B - US ASCII (default)
  • +
  • A - UK ASCII: # replaced with £
  • +
  • 0 - Symbols and basic line drawing (standard DEC alternate character set)
  • +
  • 1 - Symbols and advanced line drawing (based on DOS codepage 437, ESPTerm specific)
  • +
+ +

+ All codepages use codes 32-127, 32 being space. A character with no entry in the active codepage + stays unchanged. +

+ +

Codepage A

+
35#

Codepage 0

+
96`
97a
98b
99c
100d
101e
102f°
103g±
104h
105i
106j
107k
108l
109m
110n
111o
112p
113q
114r
115s
116t
117u
118v
119w
120x
121y
122z
123{π
124|
125}
126~·

Codepage 1

+
33!
34"
35#
36$
37%
38&
39'
40(
41)
42*
43+
44,
45-
46.
47/
480
491
502
513
524
535
546
557
568
579
58:
59;
60<
61=
62>
63?
64@
65A
66B
67C
68D
69E
70F
71G
72H
73I
74J
75K
76L
77M
78N
79O
80P
81Q
82R
83S
84T
85U
86V
87W
88X
89Y
90Z
91[
92\
93]
94^
95_
96`
97a
98b
99c
100d
101e
102f
103g
104h
105i
106j
107k
108l
109m
110n
111o
112p
113q
114rr
115ss
116tt
117uu
118vv
119ww
120xx
121yy
122zz
123{{
124||
125}
126~
+

Switching commands

+ +

+ There are two character set slots, G0 and G1. + Those slots are selected as active using ASCII codes Shift In and Shift Out (those originally served for shifting + a red-black typewriter tape). Often only G0 is used for simplicity. +

+ +

+ Each slot (G0 and G1) can have a different codepage assigned. G0 and G1 and the active slot number are + saved and restored with the cursor and cleared with a screen reset (\ec). +

+ +

The following commands are used:

+ + + + + + + + + + + + + + + + + + + + +
CodeMeaning
\e(xSet G0 = codepage x
\e)xSet G1 = codepage x
SO (14)Activate G0
SI (15)Activate G1
+
+
+ +
+

Commands: Style SGR

+ +
+

+ All text attributes are set using SGR commands like \e[10;20;30m, with up to 10 numbers separated by semicolons. + To restore all attributes to their default states, use SGR 0: \e[0m or \e[m. +

+ +

Those are the supported text attributes SGR codes:

+ + + + + + + + + + + + + +
StyleEnableDisable
Bold121, 22
Faint222
Italic323
Underlined424
Blink525
Inverse727
Striked929
𝔉𝔯𝔞𝔨𝔱𝔲𝔯2023
+
+
+ + +
+

Commands: Color SGR

+ +
+

+ Colors are set using SGR commands (like \e[10;20;30m). The following tables list the SGR codes to use. + Selected colors are used for any new text entered, as well as for empty space when using line and screen clearing commands. + The configured default colors can be restored using SGR 39 for foreground and SGR 49 for background. +

+ +

+ The actual color representation depends on a color theme which + can be selected in Terminal Settings. +

+ +

Foreground colors

+ +
+ 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 +
+ +
+ 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 +
+ +

Background colors

+ +
+ 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 +
+ +
+ 100 + 101 + 102 + 103 + 104 + 105 + 106 + 107 +
+
+
+ +
+

Commands: Cursor Functions

+ +
+

+ The coordinates are 1-based, origin is top left. The cursor can move within the entire screen, + or in the active Scrolling Region if Origin Mode is enabled. +

+ +

After writing a character, the cursor advances to the right. If it has reached the end of the row, + it stays on the same line, but writing the next character makes it jump to the start of the next + line first, scrolling up if needed. If Auto-wrap mode is disabled, the cursor never wraps or scrolls + the screen. +

+ +

+ Legend: + Italic letters such as n are ASCII numbers that serve as arguments, separated with a semicolon. + If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command. +

+ +

Movement

+ + + + + + + + + + + + + + + + + + + + + +
CodeMeaning
+ + \e[nA
\e[nB
\e[nC
\e[nD +
+
Move cursor up (A), down (B), right (C), left (D)
+ + \e[nF
\e[nE +
+
Go n lines up (F) or down (E), start of line
+ + \e[rd
\e[cG
\e[r;cH +
+
+ Go to absolute position - row (d), column (G), or both (H). Use \e[H to go to 1,1. +
+ \e[6n + + Query cursor position. Sent back as \e[r;cR. +
+ +

Save / restore

+ + + + + + + + + + + + + +
CodeMeaning
+ + \e[s
\e[u +
+
Save (s) or restore (u) cursor position
+ + \e7
\e8 +
+
Save (7) or restore (8) cursor position and attributes
+ +

Scrolling Region

+ + + + + + + + + + + + + + + + + +
CodeMeaning
+ \e[a;br + + Set scrolling region to rows a through b and go to 1,1. By default, the + scrolling region spans the entire screen height. The cursor can leave the region using + absolute position commands, unless Origin Mode (see below) is active. +
+ + \e[?6h
\e[?6l +
+
+ Enable (h) or disable (l) Origin Mode and go to 1,1. In Origin Mode, all coordinates + are relative to the Scrolling Region and the cursor can't leave the region. +
+ + \e[nS
\e[nT +
+
+ Move contents of the Scrolling Region up (S) or down (T), pad with empty + lines of the current background color. This is similar to what happens when AutoWrap + is enabled and some text is printed at the very end of the screen. +
+ +

Tab stops

+ + + + + + + + + + + + + + + + + +
CodeMeaning
+ \eH + + Set tab stop at the current column. There are, by default, tabs every 8 columns. +
+ + \e[nI
\e[nZ +
+
Advance (I) or go back (Z) n tab stops or end/start of line. ASCII TAB (9) is equivalent to \e[1I
+ + \e[0g
\e[3g
+
Clear tab stop at the current column (0), or all columns (3).
+ +

Other options

+ + + + + + + + + + + + + +
CodeMeaning
+ + \e[?7h
\e[?7l +
+
Enable (h) or disable (l) cursor auto-wrap and screen auto-scroll
+ + \e[?25h
\e[?25l +
+
Show (h) or hide (l) the cursor
+
+
+ +
+

Commands: Screen Functions

+ +
+

+ Legend: + Italic letters such as n are ASCII numbers that serve as arguments, separated with a semicolon. + If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command. +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
CodeMeaning
+ \e[mJ + + Clear part of screen. m: 0 - from cursor, 1 - to cursor, 2 - all +
+ \e[mK + + Erase part of line. m: 0 - from cursor, 1 - to cursor, 2 - all +
+ \e[nX + Erase n characters in line. +
+ + \e[nL
\e[nM +
+
+ Insert (L) or delete (M) n lines. Following lines are pulled up or pushed down. +
+ + \e[n@
\e[nP +
+
+ Insert (@) or delete (P) n characters. The rest of the line is pulled left or pushed right. + Characters going past the end of line are lost. +
+
+
+ +
+

Commands: System Functions

+ +
+

+ It's possible to dynamically change the screen title text and action button labels. + Setting an empty label to a button makes it look disabled. The buttons send ASCII 1-5 when clicked. + Those changes are not retained after restart. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeMeaning
\ec + Clear screen, reset attributes and cursor. + The screen size, title and button labels remain unchanged. +
\e[5n + Query device status, ESPTerm replies with \e[0n "device is OK". + Can be used to check if the terminal has booted up and is ready to receive commands. +
CAN (24) + This ASCII code is not a command, but is sent by ESPTerm when it becomes ready to receive commands. + When this code is received on the UART, it means ESPTerm has restarted and is ready. Use this to detect + spontaneous restarts which require a full screen repaint. +
\e]0;t\aSet screen title to t (this is a standard OSC command)
+ + \e]80+n;t\a + + + Set label for button n = 1-5 (code 81-85) to t - e.g.\e]81;Yes\a + sets the first button text to "Yes". +
+ + \e]90+n;m\a + + + Set message for button n = 1-5 (code 81-85) to m - e.g.\e]94;iv\a + sets the 3rd button to send string "iv" when pressed. +
+ + \e[?800h
\e[?800l +
+
+ Show (h) or hide (l) action buttons (the blue buttons under the screen). +
+ + \e[?801h
\e[?801l +
+
+ Show (h) or hide (l) menu/help links under the screen. +
+ + \e[12h
\e[12l +
+
+ Enable (h) or disable (l) Send-Receive Mode (SRM). + SRM is the opposite of Local Echo, meaning \e[12h disables and \e[12l enables Local Echo. +
\e[8;r;ctSet screen size to r rows and c columns (this is a command borrowed from Xterm)
+
+
+ + + + +
+ + + +
+ +
+ + + diff --git a/img/cvut.svg b/img/cvut.svg new file mode 100755 index 0000000..dc015d3 --- /dev/null +++ b/img/cvut.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/img/loader.gif b/img/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..dfb5362605d0ed553ed2eb66987b6ef88ad9ac27 GIT binary patch literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_)naswe?5R~UqMJTodxey=~NCa(Y#3i{TBv2*Nh#=b) zL=g~MmWL=*MtQV=mI_)6B83!0R4lKkL0Awa>g-tSx^1UAyUPaK&d&a^-PxJ;kDkBJ z%sKO!-}!z|bgV2SRO1G?0p9`uhl4CFEqQsZ2@88op-`rzY&RH;K|$*h6Srh#nS6YF zgM&AurfQAGOtpGjM1&MYU9?&}G&F3@8lTb8(K~l$&Ye3S6C*!*v^FOvx1-||9*=+G zL_=R+pG+3R=cB#7y<=nJz^}I4msZ3_B_(1Jil`v}K%$f5AoqWwShh}M)}@K}7|jN; zCPS0^c9tg1-?&qs1-L&a(ICez-{mVUBLLu1c!4Q~OQ({F1zL+lG}LO&4VQGh8LsQK zhrFKHw!gqHvu9hm*T!vu*YxYqp;RLJwEF`g6o3MGs`Pwh&k7}!MJrS>V2BA2oCT=i z2px({E26R38X@RftRp4m0rfeVc@*hZ9gCBLaabm5RoUCZA3CEuG>#(i47^Rb?+*z zw8(>TQs0@9@puFd31AJp(348p?~tBM(+EFrG@uaENk16@-<5@I`Uv`hciPp9TK2Wc z5}5vH+OTw`VSuCCbHCR%|2IA0PFF%Za@8++m=MfR0|+7(MQYO=KpAXSZcPo}O-d;= znvG3O4Cz)ii(|$_t{)>V072jIDF*f+FcJS`0287;={S5fQqaRE|Qr#e!+#9 zP9DW`smh@dmgHSjkjj~)jPt9i(9z>s3PbLjZ#}z<7R?weshT}my4AjTjP7sGb&c(6 zzRGzu!ZBh$H$?w0(C&94|I6oYAfP zlR6rQ`y*jI25%mQ@L0TjSjxoZQ&ZrC?L0C!$0c8%b)KK&)v7Mr1P>5CvCk$%pU^(V zKkEmdAeY-!s((Bix42T=DY72<)L3nW`Ovyu&qxC<#V`i-StfT3#Us)K*%PnmYvepf^ ze_wqNs%J^$$3p)4ohS7ddu`XL_8a?f$0&)!2(s_rk?n{isT;qf-EkB#DcqpwCcME@Vhj2 zF2VCJ32i%e$TZ2;M@q>)55K77vkw_jK`W`Toy3Rsn%6RH)&r}qY~^z{1Wd%7_Gmp7 zwL@0nt+^+etrh=Qr$0yndC-r+o%H=K(^{3U*kbo!Otn#}{IYse_IqqhD{!=bU1us$ z`n3Df3^m|IUUGUq4OqE55~6Pdbf~~tvD|Kkbb2O6szqEup@z*mXdq#fd_a@~Wag&= z)NB^#V=1TE;7IU%_GcJ7pFqrJSR#)ah#wf>tC;LVa(3MWluZV+nG;fGHaI^qYnYo~ zFl@TDIBkf_9-p5w4jV@1ucsvRPYhesQg1O0nL^7+i&`Wb^;-IwU`j7Yz93; z) UyME?^qvFCG6&JSrKYF?T1>ho$iU0rr literal 0 HcmV?d00001 diff --git a/img/vt100.jpg b/img/vt100.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70460424146a7244d0c97278296eb631af97826c GIT binary patch literal 11088 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 Jl5KyO|6gXHiYfpA literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 493021b..fbaa51d 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -this is a test file + \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..fb25582 --- /dev/null +++ b/js/app.js @@ -0,0 +1,2533 @@ +/*!chibi 3.0.7, Copyright 2012-2016 Kyle Barrow, released under MIT license */ + +// MODIFIED VERSION. +(function () { + 'use strict'; + + var readyfn = [], + loadedfn = [], + domready = false, + pageloaded = false, + d = document, + w = window; + + // Fire any function calls on ready event + function fireReady() { + var i; + domready = true; + for (i = 0; i < readyfn.length; i += 1) { + readyfn[i](); + } + readyfn = []; + } + + // Fire any function calls on loaded event + function fireLoaded() { + var i; + pageloaded = true; + // For browsers with no DOM loaded support + if (!domready) { + fireReady(); + } + for (i = 0; i < loadedfn.length; i += 1) { + loadedfn[i](); + } + loadedfn = []; + } + + // Check DOM ready, page loaded + if (d.addEventListener) { + // Standards + d.addEventListener('DOMContentLoaded', fireReady, false); + w.addEventListener('load', fireLoaded, false); + } else if (d.attachEvent) { + // IE + d.attachEvent('onreadystatechange', fireReady); + // IE < 9 + w.attachEvent('onload', fireLoaded); + } else { + // Anything else + w.onload = fireLoaded; + } + + // Utility functions + + // Loop through node array + function nodeLoop(fn, nodes) { + var i; + // Good idea to walk up the DOM + for (i = nodes.length - 1; i >= 0; i -= 1) { + fn(nodes[i]); + } + } + + // Convert to camel case + function cssCamel(property) { + return property.replace(/-\w/g, function (result) { + return result.charAt(1).toUpperCase(); + }); + } + + // Get computed style + function computeStyle(elm, property) { + // IE, everything else or null + return (elm.currentStyle) ? elm.currentStyle[cssCamel(property)] : (w.getComputedStyle) ? w.getComputedStyle(elm, null).getPropertyValue(property) : null; + + } + + // Returns URI encoded query string pair + function queryPair(name, value) { + return encodeURIComponent(name).replace(/%20/g, '+') + '=' + encodeURIComponent(value).replace(/%20/g, '+'); + } + + // Set CSS, important to wrap in try to prevent error thown on unsupported property + function setCss(elm, property, value) { + try { + elm.style[cssCamel(property)] = value; + } catch (e) { + } + } + + // Show CSS + function showCss(elm) { + elm.style.display = ''; + // For elements still hidden by style block + if (computeStyle(elm, 'display') === 'none') { + elm.style.display = 'block'; + } + } + + // Serialize form & JSON values + function serializeData(nodes) { + var querystring = '', subelm, i, j; + if (nodes.constructor === Object) { // Serialize JSON data + for (subelm in nodes) { + if (nodes.hasOwnProperty(subelm)) { + if (nodes[subelm].constructor === Array) { + for (i = 0; i < nodes[subelm].length; i += 1) { + querystring += '&' + queryPair(subelm, nodes[subelm][i]); + } + } else { + querystring += '&' + queryPair(subelm, nodes[subelm]); + } + } + } + } else { // Serialize node data + nodeLoop(function (elm) { + if (elm.nodeName === 'FORM') { + for (i = 0; i < elm.elements.length; i += 1) { + subelm = elm.elements[i]; + + if (!subelm.disabled) { + switch (subelm.type) { + // Ignore buttons, unsupported XHR 1 form fields + case 'button': + case 'image': + case 'file': + case 'submit': + case 'reset': + break; + + case 'select-one': + if (subelm.length > 0) { + querystring += '&' + queryPair(subelm.name, subelm.value); + } + break; + + case 'select-multiple': + for (j = 0; j < subelm.length; j += 1) { + if (subelm[j].selected) { + querystring += '&' + queryPair(subelm.name, subelm[j].value); + } + } + break; + + case 'checkbox': + case 'radio': + if (subelm.checked) { + querystring += '&' + queryPair(subelm.name, subelm.value); + } + break; + + // Everything else including shinny new HTML5 input types + default: + querystring += '&' + queryPair(subelm.name, subelm.value); + } + } + } + } + }, nodes); + } + // Tidy up first & + return (querystring.length > 0) ? querystring.substring(1) : ''; + } + + // Class helper + function classHelper(classes, action, nodes) { + var classarray, search, i, replace, has = false; + if (classes) { + // Trim any whitespace + classarray = classes.split(/\s+/); + nodeLoop(function (elm) { + for (i = 0; i < classarray.length; i += 1) { + var clz = classarray[i]; + if (action === 'remove') { + elm.classList.remove(clz); + } + else if (action === 'add') { + elm.classList.add(clz); + } + else if (action === 'toggle') { + elm.classList.toggle(clz); + } + else if (action === 'has') { + if (elm.classList.contains(clz)) { + has = true; + break; + } + } + + // search = new RegExp('\\b' + classarray[i] + '\\b', 'g'); + // replace = new RegExp(' *' + classarray[i] + '\\b', 'g'); + // if (action === 'remove') { + // elm.className = elm.className.replace(search, ''); + // } else if (action === 'toggle') { + // elm.className = (elm.className.match(search)) ? elm.className.replace(replace, '') : elm.className + ' ' + classarray[i]; + // } else if (action === 'has') { + // if (elm.className.match(search)) { + // has = true; + // break; + // } + // } + } + }, nodes); + } + return has; + } + + // HTML insertion helper + function insertHtml(value, position, nodes) { + var tmpnodes, tmpnode; + if (value) { + nodeLoop(function (elm) { + // No insertAdjacentHTML support for FF < 8 and IE doesn't allow insertAdjacentHTML table manipulation, so use this instead + // Convert string to node. We can't innerHTML on a document fragment + tmpnodes = d.createElement('div'); + tmpnodes.innerHTML = value; + while ((tmpnode = tmpnodes.lastChild) !== null) { + // Catch error in unlikely case elm has been removed + try { + if (position === 'before') { + elm.parentNode.insertBefore(tmpnode, elm); + } else if (position === 'after') { + elm.parentNode.insertBefore(tmpnode, elm.nextSibling); + } else if (position === 'append') { + elm.appendChild(tmpnode); + } else if (position === 'prepend') { + elm.insertBefore(tmpnode, elm.firstChild); + } + } catch (e) { + break; + } + } + }, nodes); + } + } + + // Get nodes and return chibi + function chibi(selector) { + var cb, nodes = [], json = false, nodelist, i; + + if (selector) { + + // Element node, would prefer to use (selector instanceof HTMLElement) but no IE support + if (selector.nodeType && selector.nodeType === 1) { + nodes = [selector]; // return element as node list + } else if (typeof selector === 'object') { + // JSON, document object or node list, would prefer to use (selector instanceof NodeList) but no IE support + json = (typeof selector.length !== 'number'); + nodes = selector; + } else if (typeof selector === 'string') { + nodelist = d.querySelectorAll(selector); + + // Convert node list to array so results have full access to array methods + // Array.prototype.slice.call not supported in IE < 9 and often slower than loop anyway + for (i = 0; i < nodelist.length; i += 1) { + nodes[i] = nodelist[i]; + } + } + } + + // Only attach nodes if not JSON + cb = json ? {} : nodes; + + // Public functions + + // Executes a function on nodes + cb.each = function (fn) { + if (typeof fn === 'function') { + nodeLoop(function (elm) { + // <= IE 8 loses scope so need to apply + return fn.apply(elm, arguments); + }, nodes); + } + return cb; + }; + // Find first + cb.first = function () { + return chibi(nodes.shift()); + }; + // Find last + cb.last = function () { + return chibi(nodes.pop()); + }; + // Find odd + cb.odd = function () { + var odds = [], i; + for (i = 0; i < nodes.length; i += 2) { + odds.push(nodes[i]); + } + return chibi(odds); + }; + // Find even + cb.even = function () { + var evens = [], i; + for (i = 1; i < nodes.length; i += 2) { + evens.push(nodes[i]); + } + return chibi(evens); + }; + // Hide node + cb.hide = function () { + nodeLoop(function (elm) { + elm.style.display = 'none'; + }, nodes); + return cb; + }; + // Show node + cb.show = function () { + nodeLoop(function (elm) { + showCss(elm); + }, nodes); + return cb; + }; + // Toggle node display + cb.toggle = function (state) { + if (typeof state != 'undefined') { // ADDED + if (state) + cb.show(); + else + cb.hide(); + } else { + nodeLoop(function (elm) { + // computeStyle instead of style.display == 'none' catches elements that are hidden via style block + if (computeStyle(elm, 'display') === 'none') { + showCss(elm); + } else { + elm.style.display = 'none'; + } + + }, nodes); + } + return cb; + }; + // Remove node + cb.remove = function () { + nodeLoop(function (elm) { + // Catch error in unlikely case elm has been removed + try { + elm.parentNode.removeChild(elm); + } catch (e) { + } + }, nodes); + return chibi(); + }; + // Get/Set CSS + cb.css = function (property, value) { + if (property) { + if (value || value === '') { + nodeLoop(function (elm) { + setCss(elm, property, value); + }, nodes); + return cb; + } + if (nodes[0]) { + if (nodes[0].style[cssCamel(property)]) { + return nodes[0].style[cssCamel(property)]; + } + if (computeStyle(nodes[0], property)) { + return computeStyle(nodes[0], property); + } + } + } + }; + // Get class(es) + cb.getClass = function () { + if (nodes[0] && nodes[0].className.length > 0) { + // Weak IE trim support + return nodes[0].className.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '').replace(/\s+/, ' '); + } + }; + // Set (replaces) classes + cb.setClass = function (classes) { + if (classes || classes === '') { + nodeLoop(function (elm) { + elm.className = classes; + }, nodes); + } + return cb; + }; + // Add class + cb.addClass = function (classes) { + classHelper(classes, 'add', nodes); + // if (classes) { + // nodeLoop(function (elm) { + // elm.className += ' ' + classes; + // }, nodes); + // } + return cb; + }; + // Remove class + cb.removeClass = function (classes) { + classHelper(classes, 'remove', nodes); + return cb; + }; + // Toggle class + cb.toggleClass = function (classes, set) { + var method = ((typeof set === 'undefined') ? 'toggle' : (+set ? 'add' : 'remove')); + classHelper(classes, method, nodes); + return cb; + }; + // Has class + cb.hasClass = function (classes) { + return classHelper(classes, 'has', nodes); + }; + // Get/set HTML + cb.html = function (value) { + if (value || value === '') { + nodeLoop(function (elm) { + elm.innerHTML = value; + }, nodes); + return cb; + } + if (nodes[0]) { + return nodes[0].innerHTML; + } + }; + // Insert HTML before selector + cb.htmlBefore = function (value) { + insertHtml(value, 'before', nodes); + return cb; + }; + // Insert HTML after selector + cb.htmlAfter = function (value) { + insertHtml(value, 'after', nodes); + return cb; + }; + // Insert HTML after selector innerHTML + cb.htmlAppend = function (value) { + insertHtml(value, 'append', nodes); + return cb; + }; + // Insert HTML before selector innerHTML + cb.htmlPrepend = function (value) { + insertHtml(value, 'prepend', nodes); + return cb; + }; + // Get/Set HTML attributes + cb.attr = function (property, value) { + if (property) { + property = property.toLowerCase(); + // IE < 9 doesn't allow style or class via get/setAttribute so switch. cssText returns prettier CSS anyway + if (typeof value !== 'undefined') {//FIXED BUG HERE + nodeLoop(function (elm) { + if (property === 'style') { + elm.style.cssText = value; + } else if (property === 'class') { + elm.className = value; + } else { + elm.setAttribute(property, value); + } + }, nodes); + return cb; + } + if (nodes[0]) { + if (property === 'style') { + if (nodes[0].style.cssText) { + return nodes[0].style.cssText; + } + } else if (property === 'class') { + if (nodes[0].className) { + return nodes[0].className; + } + } else { + if (nodes[0].getAttribute(property)) { + return nodes[0].getAttribute(property); + } + } + } + } + }; + // Get/Set HTML data property + cb.data = function (key, value) { + if (key) { + return cb.attr('data-' + key, value); + } + }; + // Get/Set form element values + cb.val = function (value) { + var values, i, j; + if (typeof value != 'undefined') { // FIXED A BUG HERE + nodeLoop(function (elm) { + switch (elm.nodeName) { + case 'SELECT': + if (typeof value === 'string' || typeof value === 'number') { + value = [value]; + } + for (i = 0; i < elm.length; i += 1) { + // Multiple select + for (j = 0; j < value.length; j += 1) { + elm[i].selected = ''; + if (elm[i].value === ""+value[j]) { + elm[i].selected = 'selected'; + break; + } + } + } + break; + case 'INPUT': + case 'TEXTAREA': + case 'BUTTON': + elm.value = value; + break; + } + }, nodes); + + return cb; + } + if (nodes[0]) { + switch (nodes[0].nodeName) { + case 'SELECT': + values = []; + for (i = 0; i < nodes[0].length; i += 1) { + if (nodes[0][i].selected) { + values.push(nodes[0][i].value); + } + } + return (values.length > 1) ? values : values[0]; + case 'INPUT': + case 'TEXTAREA': + case 'BUTTON': + return nodes[0].value; + } + } + }; + // Return matching checked checkbox or radios + cb.checked = function (check) { + if (typeof check === 'boolean') { + nodeLoop(function (elm) { + if (elm.nodeName === 'INPUT' && (elm.type === 'checkbox' || elm.type === 'radio')) { + elm.checked = check; + } + }, nodes); + return cb; + } + if (nodes[0] && nodes[0].nodeName === 'INPUT' && (nodes[0].type === 'checkbox' || nodes[0].type === 'radio')) { + return (!!nodes[0].checked); + } + }; + // Add event handler + cb.on = function (event, fn) { + if (selector === w || selector === d) { + nodes = [selector]; + } + nodeLoop(function (elm) { + if (d.addEventListener) { + elm.addEventListener(event, fn, false); + } else if (d.attachEvent) { + // <= IE 8 loses scope so need to apply, we add this to object so we can detach later (can't detach anonymous functions) + elm[event + fn] = function () { + return fn.apply(elm, arguments); + }; + elm.attachEvent('on' + event, elm[event + fn]); + } + }, nodes); + return cb; + }; + // Remove event handler + cb.off = function (event, fn) { + if (selector === w || selector === d) { + nodes = [selector]; + } + nodeLoop(function (elm) { + if (d.addEventListener) { + elm.removeEventListener(event, fn, false); + } else if (d.attachEvent) { + elm.detachEvent('on' + event, elm[event + fn]); + // Tidy up + elm[event + fn] = null; + } + }, nodes); + return cb; + }; + return cb; + } + + // Basic XHR + chibi.ajax = function (options) { // if options is a number, it's timeout in ms + var opts = extend({ + method: 'GET', + nocache: true, + timeout: 5000, + loader: true, + callback: null + }, options); + opts.method = opts.method.toUpperCase(); + + var loaderFn = opts.loader ? chibi._loader : function(){}; + var url = opts.url; + var method = opts.method; + var query = null; + + if (opts.data) { + query = serializeData(opts.data); + } + + if (query && (method === 'GET')) { + url += (url.indexOf('?') === -1) ? '?' + query : '&' + query; + query = null; + } + + var xhr = new XMLHttpRequest(); + + // prevent caching + if (opts.nocache) { + var ts = (+(new Date())).toString(36); + url += ((url.indexOf('?') === -1) ? '?' : '&') + '_=' + ts; + } + + loaderFn(true); + + xhr.open(method, url, true); + xhr.timeout = opts.timeout; + + // Abort after given timeout + var abortTmeo = setTimeout(function () { + console.error("XHR timed out."); + xhr.abort(); + loaderFn(false); + }, opts.timeout + 10); + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + loaderFn(false); + + opts.callback && opts.callback(xhr.responseText, xhr.status); + + clearTimeout(abortTmeo); + } + }; + + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + if (method === 'POST') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + xhr.send(query); + return xhr; + }; + + chibi._loader = function(){}; + + // Alias to cb.ajax(url, 'get', callback) + chibi.get = function (url, callback, opts) { + opts = opts || {}; + opts.url = url; + opts.callback = callback; + opts.method = 'GET'; + return chibi.ajax(opts); + }; + + // Alias to cb.ajax(url, 'post', callback) + chibi.post = function (url, callback, opts) { + opts = opts || {}; + opts.url = url; + opts.callback = callback; + opts.method = 'POST'; + return chibi.ajax(opts); + }; + + // Fire on DOM ready + chibi.ready = function (fn) { + if (fn) { + if (domready) { + fn(); + return chibi; + } else { + readyfn.push(fn); + } + } + }; + + // Fire on page loaded + chibi.loaded = function (fn) { + if (fn) { + if (pageloaded) { + fn(); + return chibi; + } else { + loadedfn.push(fn); + } + } + }; + + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + chibi.htmlEscape = function(string) { + return String(string).replace(/[&<>"'`=\/]/g, function (s) { + return entityMap[s]; + }); + }; + + // Set Chibi's global namespace here ($) + w.$ = chibi; +}()); +// keymaster.js +// (c) 2011-2013 Thomas Fuchs +// keymaster.js may be freely distributed under the MIT license. + +;(function(global){ + var k, + _handlers = {}, + _mods = { 16: false, 18: false, 17: false, 91: false }, + _scope = 'all', + // modifier keys + _MODIFIERS = { + '⇧': 16, shift: 16, + '⌥': 18, alt: 18, option: 18, + '⌃': 17, ctrl: 17, control: 17, + '⌘': 91, command: 91 + }, + // special keys + _MAP = { + backspace: 8, tab: 9, clear: 12, + enter: 13, 'return': 13, + esc: 27, escape: 27, space: 32, + left: 37, up: 38, + right: 39, down: 40, + del: 46, 'delete': 46, + home: 36, end: 35, + pageup: 33, pagedown: 34, + ',': 188, '.': 190, '/': 191, + '`': 192, '-': 189, '=': 187, + ';': 186, '\'': 222, + '[': 219, ']': 221, '\\': 220, + // added: + insert: 45, + np_0: 96, np_1: 97, np_2: 98, np_3: 99, np_4: 100, np_5: 101, + np_6: 102, np_7: 103, np_8: 104, np_9: 105, np_mul: 106, + np_add: 107, np_sub: 109, np_point: 110, np_div: 111, numlock: 144, + }, + code = function(x){ + return _MAP[x] || x.toUpperCase().charCodeAt(0); + }, + _downKeys = []; + + for(k=1;k<20;k++) _MAP['f'+k] = 111+k; + + // IE doesn't support Array#indexOf, so have a simple replacement + function index(array, item){ + var i = array.length; + while(i--) if(array[i]===item) return i; + return -1; + } + + // for comparing mods before unassignment + function compareArray(a1, a2) { + if (a1.length != a2.length) return false; + for (var i = 0; i < a1.length; i++) { + if (a1[i] !== a2[i]) return false; + } + return true; + } + + var modifierMap = { + 16:'shiftKey', + 18:'altKey', + 17:'ctrlKey', + 91:'metaKey' + }; + function updateModifierKey(event) { + for(k in _mods) _mods[k] = event[modifierMap[k]]; + }; + + function isModifierPressed(mod) { + if (mod=='control'||mod=='ctrl') return _mods[17]; + if (mod=='shift') return _mods[16]; + if (mod=='meta') return _mods[91]; + if (mod=='alt') return _mods[18]; + return false; + } + + // handle keydown event + function dispatch(event) { + var key, handler, k, i, modifiersMatch, scope; + key = event.keyCode; + + if (index(_downKeys, key) == -1) { + _downKeys.push(key); + } + + // if a modifier key, set the key. property to true and return + if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko + if(key in _mods) { + _mods[key] = true; + // 'assignKey' from inside this closure is exported to window.key + for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true; + return; + } + updateModifierKey(event); + + // see if we need to ignore the keypress (filter() can can be overridden) + // by default ignore key presses if a select, textarea, or input is focused + if(!assignKey.filter.call(this, event)) return; + + // abort if no potentially matching shortcuts found + if (!(key in _handlers)) return; + + scope = getScope(); + + // for each potential shortcut + for (i = 0; i < _handlers[key].length; i++) { + handler = _handlers[key][i]; + + // see if it's in the current scope + if(handler.scope == scope || handler.scope == 'all'){ + // check if modifiers match if any + modifiersMatch = handler.mods.length > 0; + for(k in _mods) + if((!_mods[k] && index(handler.mods, +k) > -1) || + (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false; + // call the handler and stop the event if neccessary + if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){ + if(handler.method(event, handler)===false){ + if(event.preventDefault) event.preventDefault(); + else event.returnValue = false; + if(event.stopPropagation) event.stopPropagation(); + if(event.cancelBubble) event.cancelBubble = true; + } + } + } + } + }; + + // unset modifier keys on keyup + function clearModifier(event){ + var key = event.keyCode, k, + i = index(_downKeys, key); + + // remove key from _downKeys + if (i >= 0) { + _downKeys.splice(i, 1); + } + + if(key == 93 || key == 224) key = 91; + if(key in _mods) { + _mods[key] = false; + for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false; + } + }; + + function resetModifiers() { + for(k in _mods) _mods[k] = false; + for(k in _MODIFIERS) assignKey[k] = false; + }; + + // parse and assign shortcut + function assignKey(key, scope, method){ + var keys, mods; + keys = getKeys(key); + if (method === undefined) { + method = scope; + scope = 'all'; + } + + // for each shortcut + for (var i = 0; i < keys.length; i++) { + // set modifier keys if any + mods = []; + key = keys[i].split('+'); + if (key.length > 1){ + mods = getMods(key); + key = [key[key.length-1]]; + } + // convert to keycode and... + key = key[0] + key = code(key); + // ...store handler + if (!(key in _handlers)) _handlers[key] = []; + _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods }); + } + }; + + // unbind all handlers for given key in current scope + function unbindKey(key, scope) { + var multipleKeys, keys, + mods = [], + i, j, obj; + + multipleKeys = getKeys(key); + + for (j = 0; j < multipleKeys.length; j++) { + keys = multipleKeys[j].split('+'); + + if (keys.length > 1) { + mods = getMods(keys); + } + + key = keys[keys.length - 1]; + key = code(key); + + if (scope === undefined) { + scope = getScope(); + } + if (!_handlers[key]) { + return; + } + for (i = 0; i < _handlers[key].length; i++) { + obj = _handlers[key][i]; + // only clear handlers if correct scope and mods match + if (obj.scope === scope && compareArray(obj.mods, mods)) { + _handlers[key][i] = {}; + } + } + } + }; + + // Returns true if the key with code 'keyCode' is currently down + // Converts strings into key codes. + function isPressed(keyCode) { + if (typeof(keyCode)=='string') { + keyCode = code(keyCode); + } + return index(_downKeys, keyCode) != -1; + } + + function getPressedKeyCodes() { + return _downKeys.slice(0); + } + + function filter(event){ + var tagName = (event.target || event.srcElement).tagName; + // ignore keypressed in any elements that support keyboard data input + return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); + } + + // initialize key. to false + for(k in _MODIFIERS) assignKey[k] = false; + + // set current scope (default 'all') + function setScope(scope){ _scope = scope || 'all' }; + function getScope(){ return _scope || 'all' }; + + // delete all handlers for a given scope + function deleteScope(scope){ + var key, handlers, i; + + for (key in _handlers) { + handlers = _handlers[key]; + for (i = 0; i < handlers.length; ) { + if (handlers[i].scope === scope) handlers.splice(i, 1); + else i++; + } + } + }; + + // abstract key logic for assign and unassign + function getKeys(key) { + var keys; + key = key.replace(/\s/g, ''); + keys = key.split(','); + if ((keys[keys.length - 1]) == '') { + keys[keys.length - 2] += ','; + } + return keys; + } + + // abstract mods logic for assign and unassign + function getMods(key) { + var mods = key.slice(0, key.length - 1); + for (var mi = 0; mi < mods.length; mi++) + mods[mi] = _MODIFIERS[mods[mi]]; + return mods; + } + + // cross-browser events + function addEvent(object, event, method) { + if (object.addEventListener) + object.addEventListener(event, method, false); + else if(object.attachEvent) + object.attachEvent('on'+event, function(){ method(window.event) }); + }; + + // set the handlers globally on document + addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48 + addEvent(document, 'keyup', clearModifier); + + // reset modifiers to false whenever the window is (re)focused. + addEvent(window, 'focus', resetModifiers); + + // store previously defined key + var previousKey = global.key; + + // restore previously defined key and return reference to our key object + function noConflict() { + var k = global.key; + global.key = previousKey; + return k; + } + + // set window.key and window.key.set/get/deleteScope, and the default filter + global.key = assignKey; + global.key.setScope = setScope; + global.key.getScope = getScope; + global.key.deleteScope = deleteScope; + global.key.filter = filter; + global.key.isPressed = isPressed; + global.key.isModifier = isModifierPressed; + global.key.getPressedKeyCodes = getPressedKeyCodes; + global.key.noConflict = noConflict; + global.key.unbind = unbindKey; + + if(typeof module !== 'undefined') module.exports = assignKey; + +})(this); +/** Make a node */ +function mk(e) {return document.createElement(e)} + +/** Find one by query */ +function qs(s) {return document.querySelector(s)} + +/** Find all by query */ +function qsa(s) {return document.querySelectorAll(s)} + +/** Convert any to bool safely */ +function bool(x) { + return (x === 1 || x === '1' || x === true || x === 'true'); +} + +/** + * Filter 'spacebar' and 'return' from keypress handler, + * and when they're pressed, fire the callback. + * use $(...).on('keypress', cr(handler)) + */ +function cr(hdl) { + return function(e) { + if (e.which == 10 || e.which == 13 || e.which == 32) { + hdl(); + } + }; +} + +/** Extend an objects with options */ +function extend(defaults, options) { + var target = {}; + + Object.keys(defaults).forEach(function(k){ + target[k] = defaults[k]; + }); + + Object.keys(options).forEach(function(k){ + target[k] = options[k]; + }); + + return target; +} + +/** Escape string for use as literal in RegExp */ +function rgxe(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +} + +/** Format number to N decimal places, output as string */ +function numfmt(x, places) { + var pow = Math.pow(10, places); + return Math.round(x*pow) / pow; +} + +/** Get millisecond timestamp */ +function msNow() { + return +(new Date); +} + +/** Get ms elapsed since msNow() */ +function msElapsed(start) { + return msNow() - start; +} + +/** Shim for log base 10 */ +Math.log10 = Math.log10 || function(x) { + return Math.log(x) / Math.LN10; +}; + +/** + * Perform a substitution in the given string. + * + * Arguments - array or list of replacements. + * Arguments numeric keys will replace {0}, {1} etc. + * Named keys also work, ie. {foo: "bar"} -> replaces {foo} with bar. + * + * Braces are added to keys if missing. + * + * @returns {String} result + */ +String.prototype.format = function () { + var out = this; + var repl = arguments; + + if (arguments.length == 1 && (Array.isArray(arguments[0]) || typeof arguments[0] == 'object')) { + repl = arguments[0]; + } + + for (var ph in repl) { + if (repl.hasOwnProperty(ph)) { + var ph_orig = ph; + + if (!ph.match(/^\{.*\}$/)) { + ph = '{' + ph + '}'; + } + + // replace all occurrences + var pattern = new RegExp(rgxe(ph), "g"); + out = out.replace(pattern, repl[ph_orig]); + } + } + + return out; +}; + +/** HTML escape */ +function e(str) { + return $.htmlEscape(str); +} + +/** Check for undefined */ +function undef(x) { + return typeof x == 'undefined'; +} + +/** Safe json parse */ +function jsp(str) { + try { + return JSON.parse(str); + } catch(e) { + console.error(e); + return null; + } +} + +/** Create a character from ASCII code */ +function Chr(n) { + return String.fromCharCode(n); +} + +/** Decode number from 2B encoding */ +function parse2B(s, i) { + return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; +} + +/** Decode number from 3B encoding */ +function parse3B(s, i) { + return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; +} + +/** Encode using 2B encoding, returns string. */ +function encode2B(n) { + var lsb, msb; + lsb = (n % 127); + n = ((n - lsb) / 127); + lsb += 1; + msb = (n + 1); + return Chr(lsb) + Chr(msb); +} + +/** Encode using 3B encoding, returns string. */ +function encode3B(n) { + var lsb, msb, xsb; + lsb = (n % 127); + n = (n - lsb) / 127; + lsb += 1; + msb = (n % 127); + n = (n - msb) / 127; + msb += 1; + xsb = (n + 1); + return Chr(lsb) + Chr(msb) + Chr(xsb); +} +/** Module for toggling a modal overlay */ +(function () { + var modal = {}; + var curCloseCb = null; + + modal.show = function (sel, closeCb) { + var $m = $(sel); + $m.removeClass('hidden visible'); + setTimeout(function () { + $m.addClass('visible'); + }, 1); + curCloseCb = closeCb; + }; + + modal.hide = function (sel) { + var $m = $(sel); + $m.removeClass('visible'); + setTimeout(function () { + $m.addClass('hidden'); + if (curCloseCb) curCloseCb(); + }, 500); // transition time + }; + + modal.init = function () { + // close modal by click outside the dialog + $('.Modal').on('click', function () { + if ($(this).hasClass('no-close')) return; // this is a no-close modal + modal.hide(this); + }); + + $('.Dialog').on('click', function (e) { + e.stopImmediatePropagation(); + }); + + // Hide all modals on esc + $(window).on('keydown', function (e) { + if (e.which == 27) { + modal.hide('.Modal'); + } + }); + }; + + window.Modal = modal; +})(); +(function (nt) { + var sel = '#notif'; + + var hideTmeo1; // timeout to start hiding (transition) + var hideTmeo2; // timeout to add the hidden class + + nt.show = function (message, timeout) { + $(sel).html(message); + Modal.show(sel); + + clearTimeout(hideTmeo1); + clearTimeout(hideTmeo2); + + if (undef(timeout)) timeout = 2500; + + hideTmeo1 = setTimeout(nt.hide, timeout); + }; + + nt.hide = function () { + var $m = $(sel); + $m.removeClass('visible'); + hideTmeo2 = setTimeout(function () { + $m.addClass('hidden'); + }, 250); // transition time + }; + + nt.init = function() { + $(sel).on('click', function() { + nt.hide(this); + }); + }; +})(window.Notify = {}); +/** Global generic init */ +$.ready(function () { + // Checkbox UI (checkbox CSS and hidden input with int value) + $('.Row.checkbox').forEach(function(x) { + var inp = x.querySelector('input'); + var box = x.querySelector('.box'); + + $(box).toggleClass('checked', inp.value); + + var hdl = function() { + inp.value = 1 - inp.value; + $(box).toggleClass('checked', inp.value) + }; + + $(x).on('click', hdl).on('keypress', cr(hdl)); + }); + + // Expanding boxes on mobile + $('.Box.mobcol,.Box.fold').forEach(function(x) { + var h = x.querySelector('h2'); + + var hdl = function() { + $(x).toggleClass('expanded'); + }; + $(h).on('click', hdl).on('keypress', cr(hdl)); + }); + + qsa('form').forEach(function(x) { + $(x).on('keypress', function(e) { + if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) { + x.submit(); + } + }) + }); + + // loader dots... + setInterval(function () { + $('.anim-dots').each(function (x) { + var $x = $(x); + var dots = $x.html() + '.'; + if (dots.length == 5) dots = '.'; + $x.html(dots); + }); + }, 1000); + + // flipping number boxes with the mouse wheel + $('input[type=number]').on('mousewheel', function(e) { + var $this = $(this); + var val = +$this.val(); + if (isNaN(val)) val = 1; + + var step = +($this.attr('step') || 1); + var min = +$this.attr('min'); + var max = +$this.attr('max'); + if(e.wheelDelta > 0) { + val += step; + } else { + val -= step; + } + + if (typeof min != 'undefined') val = Math.max(val, +min); + if (typeof max != 'undefined') val = Math.min(val, +max); + $this.val(val); + + if ("createEvent" in document) { + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", false, true); + $this[0].dispatchEvent(evt); + } else { + $this[0].fireEvent("onchange"); + } + + e.preventDefault(); + }); + + var errAt = location.search.indexOf('err='); + if (errAt !== -1 && qs('.Box.errors')) { + var errs = location.search.substr(errAt+4).split(','); + var hres = []; + errs.forEach(function(er) { + var lbl = qs('label[for="'+er+'"]'); + if (lbl) { + lbl.classList.add('error'); + hres.push(lbl.childNodes[0].textContent.trim().replace(/: ?$/, '')); + } else { + hres.push(er); + } + }); + + qs('.Box.errors .list').innerHTML = hres.join(', '); + qs('.Box.errors').classList.remove('hidden'); + } + + Modal.init(); + Notify.init(); + + // remove tabindixes from h2 if wide + if (window.innerWidth > 550) { + qsa('.Box h2').forEach(function (x) { + x.removeAttribute('tabindex'); + }); + + // brand works as a link back to term in widescreen mode + var br = qs('#brand'); + br && br.addEventListener('click', function() { + location.href='/'; // go to terminal + }); + } +}); + +$._loader = function(vis) { + $('#loader').toggleClass('show', vis); +}; + +function showPage() { + $('#content').addClass('load'); +} + +$.ready(function() { + if (window.noAutoShow !== true) { + setTimeout(function () { + showPage(); + }, 1); + } +}); + + +/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ +if (!String.fromCodePoint) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var stringFromCharCode = String.fromCharCode; + var floor = Math.floor; + var fromCodePoint = function() { + var MAX_SIZE = 0x4000; + var codeUnits = []; + var highSurrogate; + var lowSurrogate; + var index = -1; + var length = arguments.length; + if (!length) { + return ''; + } + var result = ''; + while (++index < length) { + var codePoint = Number(arguments[index]); + if ( + !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` + codePoint < 0 || // not a valid Unicode code point + codePoint > 0x10FFFF || // not a valid Unicode code point + floor(codePoint) != codePoint // not an integer + ) { + throw RangeError('Invalid code point: ' + codePoint); + } + if (codePoint <= 0xFFFF) { // BMP code point + codeUnits.push(codePoint); + } else { // Astral code point; split in surrogate halves + // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + codePoint -= 0x10000; + highSurrogate = (codePoint >> 10) + 0xD800; + lowSurrogate = (codePoint % 0x400) + 0xDC00; + codeUnits.push(highSurrogate, lowSurrogate); + } + if (index + 1 == length || codeUnits.length > MAX_SIZE) { + result += stringFromCharCode.apply(null, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + if (defineProperty) { + defineProperty(String, 'fromCodePoint', { + 'value': fromCodePoint, + 'configurable': true, + 'writable': true + }); + } else { + String.fromCodePoint = fromCodePoint; + } + }()); +} +// Generated from PHP locale file +var _tr = { + "wifi.connected_ip_is": "Connected, IP is ", + "wifi.not_conn": "Not connected.", + "wifi.enter_passwd": "Enter password for \":ssid:\"" +}; + +function tr(key) { return _tr[key] || '?'+key+'?'; } +(function(w) { + var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2']; + var curSSID; + + // Get XX % for a slider input + function rangePt(inp) { + return Math.round(((inp.value / inp.max)*100)) + '%'; + } + + // Display selected STA SSID etc + function selectSta(name, password, ip) { + $('#sta_ssid').val(name); + $('#sta_password').val(password); + + $('#sta-nw').toggleClass('hidden', name.length == 0); + $('#sta-nw-nil').toggleClass('hidden', name.length > 0); + + $('#sta-nw .essid').html(e(name)); + var nopw = undef(password) || password.length == 0; + $('#sta-nw .passwd').toggleClass('hidden', nopw); + $('#sta-nw .nopasswd').toggleClass('hidden', !nopw); + $('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn')); + } + + /** Update display for received response */ + function onScan(resp, status) { + //var ap_json = { + // "result": { + // "inProgress": "0", + // "APs": [ + // {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"}, + // {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"}, + // ] + // } + //}; + + if (status != 200) { + // bad response + rescan(5000); // wait 5sm then retry + return; + } + + try { + resp = JSON.parse(resp); + } catch (e) { + console.log(e); + rescan(5000); + return; + } + + var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0); + rescan(done ? 15000 : 1000); + if (!done) return; // no redraw yet + + // clear the AP list + var $list = $('#ap-list'); + // remove old APs + $('#ap-list .AP').remove(); + + $list.toggleClass('hidden', !done); + $('#ap-loader').toggleClass('hidden', done); + + // scan done + resp.result.APs.sort(function (a, b) { + return b.rssi - a.rssi; + }).forEach(function (ap) { + ap.enc = parseInt(ap.enc); + + if (ap.enc > 4) return; // hide unsupported auths + + var item = mk('div'); + + var $item = $(item) + .data('ssid', ap.essid) + .data('pwd', ap.enc) + .attr('tabindex', 0) + .addClass('AP'); + + // mark current SSID + if (ap.essid == curSSID) { + $item.addClass('selected'); + } + + var inner = mk('div'); + $(inner).addClass('inner') + .htmlAppend('
{0}
'.format(ap.rssi_perc)) + .htmlAppend('
{0}
'.format($.htmlEscape(ap.essid))) + .htmlAppend('
{0}
'.format(authStr[ap.enc])); + + $item.on('click', function () { + var $th = $(this); + + var conn_ssid = $th.data('ssid'); + var conn_pass = ''; + + if (+$th.data('pwd')) { + // this AP needs a password + conn_pass = prompt(tr("wifi.enter_passwd").replace(":ssid:", conn_ssid)); + if (!conn_pass) return; + } + + $('#sta_password').val(conn_pass); + $('#sta_ssid').val(conn_ssid); + selectSta(conn_ssid, conn_pass, ''); + }); + + + item.appendChild(inner); + $list[0].appendChild(item); + }); + } + + function startScanning() { + $('#ap-loader').removeClass('hidden'); + $('#ap-scan').addClass('hidden'); + $('#ap-loader .anim-dots').html('.'); + + scanAPs(); + } + + /** Ask the CGI what APs are visible (async) */ + function scanAPs() { + if (_demo) { + onScan(_demo_aps, 200); + } else { + $.get('http://' + _root + '/cfg/wifi/scan', onScan); + } + } + + function rescan(time) { + setTimeout(scanAPs, time); + } + + /** Set up the WiFi page */ + function wifiInit(cfg) { + // Update slider value displays + $('.Row.range').forEach(function(x) { + var inp = x.querySelector('input'); + var disp1 = x.querySelector('.x-disp1'); + var disp2 = x.querySelector('.x-disp2'); + var t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + $(inp).on('input', function() { + t = rangePt(inp); + $(disp1).html(t); + $(disp2).html(t); + }); + }); + + // Forget STA credentials + $('#forget-sta').on('click', function() { + selectSta('', '', ''); + return false; + }); + + selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip); + curSSID = cfg.sta_active_ssid; + } + + w.init = wifiInit; + w.startScanning = startScanning; +})(window.WiFi = {}); +/** Handle connections */ +var Conn = (function() { + var ws; + var heartbeatTout; + var pingIv; + var xoff = false; + var autoXoffTout; + + function onOpen(evt) { + console.log("CONNECTED"); + } + + function onClose(evt) { + console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting..."); + setTimeout(function() { + init(); + }, 200); + // this happens when the buffer gets fucked up via invalid unicode. + // we basically use polling instead of socket then + } + + function onMessage(evt) { + try { + // . = heartbeat + switch (evt.data.charAt(0)) { + case 'B': + case 'T': + case 'S': + Screen.load(evt.data); + break; + + case '-': + //console.log('xoff'); + xoff = true; + autoXoffTout = setTimeout(function(){xoff=false;}, 250); + break; + + case '+': + //console.log('xon'); + xoff = false; + clearTimeout(autoXoffTout); + break; + } + heartbeat(); + } catch(e) { + console.error(e); + } + } + + function canSend() { + return !xoff; + } + + function doSend(message) { + if (_demo) { + console.log("TX: ", message); + return true; // Simulate success + } + if (xoff) { + // TODO queue + console.log("Can't send, flood control."); + return false; + } + + if (!ws) return false; // for dry testing + if (ws.readyState != 1) { + console.error("Socket not ready"); + return false; + } + if (typeof message != "string") { + message = JSON.stringify(message); + } + ws.send(message); + return true; + } + + function init() { + if (_demo) { + console.log("Demo mode!"); + Screen.load(_demo_screen); + showPage(); + return; + } + heartbeat(); + + ws = new WebSocket("ws://"+_root+"/term/update.ws"); + ws.onopen = onOpen; + ws.onclose = onClose; + ws.onmessage = onMessage; + + console.log("Opening socket."); + + // Ask for initial data + $.get('http://'+_root+'/term/init', function(resp, status) { + if (status !== 200) location.reload(true); + console.log("Data received!"); + Screen.load(resp); + heartbeat(); + + showPage(); + }); + } + + function heartbeat() { + clearTimeout(heartbeatTout); + heartbeatTout = setTimeout(heartbeatFail, 2000); + } + + function heartbeatFail() { + console.error("Heartbeat lost, probing server..."); + pingIv = setInterval(function() { + console.log("> ping"); + $.get('http://'+_root+'/system/ping', function(resp, status) { + if (status == 200) { + clearInterval(pingIv); + console.info("Server ready, reloading page..."); + location.reload(); + } + }, { + timeout: 100, + }); + }, 500); + } + + return { + ws: null, + init: init, + send: doSend, + canSend: canSend, // check flood control + }; +})(); +/** + * User input + * + * --- Rx messages: --- + * S - screen content (binary encoding of the entire screen with simple compression) + * T - text labels - Title and buttons, \0x01-separated + * B - beep + * . - heartbeat + * + * --- Tx messages --- + * s - string + * b - action button + * p - mb press + * r - mb release + * m - mouse move + */ +var Input = (function() { + var opts = { + np_alt: false, + cu_alt: false, + fn_alt: false, + mt_click: false, + mt_move: false, + no_keys: false, + }; + + /** Send a literal message */ + function sendStrMsg(str) { + return Conn.send("s"+str); + } + + /** Send a button event */ + function sendBtnMsg(n) { + Conn.send("b"+Chr(n)); + } + + /** Fn alt choice for key message */ + function fa(alt, normal) { + return opts.fn_alt ? alt : normal; + } + + /** Cursor alt choice for key message */ + function ca(alt, normal) { + return opts.cu_alt ? alt : normal; + } + + /** Numpad alt choice for key message */ + function na(alt, normal) { + return opts.np_alt ? alt : normal; + } + + function _bindFnKeys() { + var keymap = { + 'tab': '\x09', + 'backspace': '\x08', + 'enter': '\x0d', + 'ctrl+enter': '\x0a', + 'esc': '\x1b', + 'up': ca('\x1bOA', '\x1b[A'), + 'down': ca('\x1bOB', '\x1b[B'), + 'right': ca('\x1bOC', '\x1b[C'), + 'left': ca('\x1bOD', '\x1b[D'), + 'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')), + 'insert': '\x1b[2~', + 'delete': '\x1b[3~', + 'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')), + 'pageup': '\x1b[5~', + 'pagedown': '\x1b[6~', + 'f1': fa('\x1bOP', '\x1b[11~'), + 'f2': fa('\x1bOQ', '\x1b[12~'), + 'f3': fa('\x1bOR', '\x1b[13~'), + 'f4': fa('\x1bOS', '\x1b[14~'), + 'f5': '\x1b[15~', // note the disconnect + 'f6': '\x1b[17~', + 'f7': '\x1b[18~', + 'f8': '\x1b[19~', + 'f9': '\x1b[20~', + 'f10': '\x1b[21~', // note the disconnect + 'f11': '\x1b[23~', + 'f12': '\x1b[24~', + 'shift+f1': fa('\x1bO1;2P', '\x1b[25~'), + 'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect + 'shift+f3': fa('\x1bO1;2R', '\x1b[28~'), + 'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect + 'shift+f5': fa('\x1b[15;2~', '\x1b[31~'), + 'shift+f6': fa('\x1b[17;2~', '\x1b[32~'), + 'shift+f7': fa('\x1b[18;2~', '\x1b[33~'), + 'shift+f8': fa('\x1b[19;2~', '\x1b[34~'), + 'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is? + 'shift+f10': fa('\x1b[21;2~', '\x1b[36~'), + 'shift+f11': fa('\x1b[22;2~', '\x1b[37~'), + 'shift+f12': fa('\x1b[23;2~', '\x1b[38~'), + 'np_0': na('\x1bOp', '0'), + 'np_1': na('\x1bOq', '1'), + 'np_2': na('\x1bOr', '2'), + 'np_3': na('\x1bOs', '3'), + 'np_4': na('\x1bOt', '4'), + 'np_5': na('\x1bOu', '5'), + 'np_6': na('\x1bOv', '6'), + 'np_7': na('\x1bOw', '7'), + 'np_8': na('\x1bOx', '8'), + 'np_9': na('\x1bOy', '9'), + 'np_mul': na('\x1bOR', '*'), + 'np_add': na('\x1bOl', '+'), + 'np_sub': na('\x1bOS', '-'), + 'np_point': na('\x1bOn', '.'), + 'np_div': na('\x1bOQ', '/'), + // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest) + }; + + for (var k in keymap) { + if (keymap.hasOwnProperty(k)) { + bind(k, keymap[k]); + } + } + } + + /** Bind a keystroke to message */ + function bind(combo, str) { + // mac fix - allow also cmd + if (combo.indexOf('ctrl+') !== -1) { + combo += ',' + combo.replace('ctrl', 'command'); + } + + // unbind possible old binding + key.unbind(combo); + + key(combo, function (e) { + if (opts.no_keys) return; + e.preventDefault(); + sendStrMsg(str) + }); + } + + /** Bind/rebind key messages */ + function _initKeys() { + // This takes care of text characters typed + window.addEventListener('keypress', function(evt) { + if (opts.no_keys) return; + var str = ''; + if (evt.key) str = evt.key; + else if (evt.which) str = String.fromCodePoint(evt.which); + if (str.length>0 && str.charCodeAt(0) >= 32) { +// console.log("Typed ", str); + sendStrMsg(str); + } + }); + + // ctrl-letter codes are sent as simple low ASCII codes + for (var i = 1; i<=26;i++) { + bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i)); + } + bind('ctrl+]', '\x1b'); // alternate way to enter ESC + bind('ctrl+\\', '\x1c'); + bind('ctrl+[', '\x1d'); + bind('ctrl+^', '\x1e'); + bind('ctrl+_', '\x1f'); + + _bindFnKeys(); + } + + // mouse button states + var mb1 = 0; + var mb2 = 0; + var mb3 = 0; + + /** Init the Input module */ + function init() { + _initKeys(); + + // Button presses + qsa('#action-buttons button').forEach(function(s) { + s.addEventListener('click', function() { + sendBtnMsg(+this.dataset['n']); + }); + }); + + // global mouse state tracking - for motion reporting + window.addEventListener('mousedown', function(evt) { + if (evt.button == 0) mb1 = 1; + if (evt.button == 1) mb2 = 1; + if (evt.button == 2) mb3 = 1; + }); + + window.addEventListener('mouseup', function(evt) { + if (evt.button == 0) mb1 = 0; + if (evt.button == 1) mb2 = 0; + if (evt.button == 2) mb3 = 0; + }); + } + + /** Prepare modifiers byte for mouse message */ + function packModifiersForMouse() { + return (key.isModifier('ctrl')?1:0) | + (key.isModifier('shift')?2:0) | + (key.isModifier('alt')?4:0) | + (key.isModifier('meta')?8:0); + } + + return { + /** Init the Input module */ + init: init, + + /** Send a literal string message */ + sendString: sendStrMsg, + + /** Enable alternate key modes (cursors, numpad, fn) */ + setAlts: function(cu, np, fn) { + if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) { + opts.cu_alt = cu; + opts.np_alt = np; + opts.fn_alt = fn; + + // rebind keys - codes have changed + _bindFnKeys(); + } + }, + + setMouseMode: function(click, move) { + opts.mt_click = click; + opts.mt_move = move; + }, + + // Mouse events + onMouseMove: function (x, y) { + if (!opts.mt_move) return; + var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0; + var m = packModifiersForMouse(); + Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); + }, + onMouseDown: function (x, y, b) { + if (!opts.mt_click) return; + if (b > 3 || b < 1) return; + var m = packModifiersForMouse(); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); + // console.log("B ",b," M ",m); + }, + onMouseUp: function (x, y, b) { + if (!opts.mt_click) return; + if (b > 3 || b < 1) return; + var m = packModifiersForMouse(); + Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); + // console.log("B ",b," M ",m); + }, + onMouseWheel: function (x, y, dir) { + if (!opts.mt_click) return; + // -1 ... btn 4 (away from user) + // +1 ... btn 5 (towards user) + var m = packModifiersForMouse(); + var b = (dir < 0 ? 4 : 5); + Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); + // console.log("B ",b," M ",m); + }, + mouseTracksClicks: function() { + return opts.mt_click; + }, + blockKeys: function(yes) { + opts.no_keys = yes; + } + }; +})(); + +var Screen = (function () { + var W = 0, H = 0; // dimensions + var inited = false; + + var cursor = { + a: false, // active (blink state) + x: 0, // 0-based coordinates + y: 0, + fg: 7, // colors 0-15 + bg: 0, + attrs: 0, + suppress: false, // do not turn on in blink interval (for safe moving) + forceOn: false, // force on unless hanging: used to keep cursor visible during move + hidden: false, // do not show (DEC opt) + hanging: false, // cursor at column "W+1" - not visible + }; + + var screen = []; + var blinkIval; + var cursorFlashStartIval; + + // Some non-bold Fraktur symbols are outside the contiguous block + var frakturExceptions = { + 'C': '\u212d', + 'H': '\u210c', + 'I': '\u2111', + 'R': '\u211c', + 'Z': '\u2128', + }; + + // for BEL + var audioCtx = null; + try { + audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)(); + } catch (er) { + console.error("No AudioContext!", er); + } + + /** Get cell under cursor */ + function _curCell() { + return screen[cursor.y*W + cursor.x]; + } + + /** Safely move cursor */ + function cursorSet(y, x) { + // Hide and prevent from showing up during the move + cursor.suppress = true; + _draw(_curCell(), false); + cursor.x = x; + cursor.y = y; + // Show again + cursor.suppress = false; + _draw(_curCell()); + } + + function alpha2fraktur(t) { + // perform substitution + if (t >= 'a' && t <= 'z') { + t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0)); + } + else if (t >= 'A' && t <= 'Z') { + // this set is incomplete, some exceptions are needed + if (frakturExceptions.hasOwnProperty(t)) { + t = frakturExceptions[t]; + } else { + t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0)); + } + } + return t; + } + + /** Update cell on display. inv = invert (for cursor) */ + function _draw(cell, inv) { + if (!cell) return; + if (typeof inv == 'undefined') { + inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y; + } + + var fg, bg, cn, t; + + fg = inv ? cell.bg : cell.fg; + bg = inv ? cell.fg : cell.bg; + + t = cell.t; + if (!t.length) t = ' '; + + cn = 'fg' + fg + ' bg' + bg; + if (cell.attrs & (1<<0)) cn += ' bold'; + if (cell.attrs & (1<<1)) cn += ' faint'; + if (cell.attrs & (1<<2)) cn += ' italic'; + if (cell.attrs & (1<<3)) cn += ' under'; + if (cell.attrs & (1<<4)) cn += ' blink'; + if (cell.attrs & (1<<5)) { + cn += ' fraktur'; + t = alpha2fraktur(t); + } + if (cell.attrs & (1<<6)) cn += ' strike'; + + cell.slot.textContent = t; + cell.elem.className = cn; + } + + /** Show entire screen */ + function _drawAll() { + for (var i = W*H-1; i>=0; i--) { + _draw(screen[i]); + } + } + + function _rebuild(rows, cols) { + W = cols; + H = rows; + + /* Build screen & show */ + var cOuter, cInner, cell, screenDiv = qs('#screen'); + + // Empty the screen node + while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild); + + screen = []; + + for(var i = 0; i < W*H; i++) { + cOuter = mk('span'); + cInner = mk('span'); + + /* Mouse tracking */ + (function() { + var x = i % W; + var y = Math.floor(i / W); + cOuter.addEventListener('mouseenter', function (evt) { + Input.onMouseMove(x, y); + }); + cOuter.addEventListener('mousedown', function (evt) { + Input.onMouseDown(x, y, evt.button+1); + }); + cOuter.addEventListener('mouseup', function (evt) { + Input.onMouseUp(x, y, evt.button+1); + }); + cOuter.addEventListener('contextmenu', function (evt) { + if (Input.mouseTracksClicks()) { + evt.preventDefault(); + } + }); + cOuter.addEventListener('mousewheel', function (evt) { + Input.onMouseWheel(x, y, evt.deltaY>0?1:-1); + return false; + }); + })(); + + /* End of line */ + if ((i > 0) && (i % W == 0)) { + screenDiv.appendChild(mk('br')); + } + /* The cell */ + cOuter.appendChild(cInner); + screenDiv.appendChild(cOuter); + + cell = { + t: ' ', + fg: 7, + bg: 0, // the colors will be replaced immediately as we receive data (user won't see this) + attrs: 0, + elem: cOuter, + slot: cInner, + x: i % W, + y: Math.floor(i / W), + }; + screen.push(cell); + _draw(cell); + } + } + + /** Init the terminal */ + function _init() { + /* Cursor blinking */ + clearInterval(blinkIval); + blinkIval = setInterval(function () { + cursor.a = !cursor.a; + if (cursor.hidden || cursor.hanging) { + cursor.a = false; + } + + if (!cursor.suppress) { + _draw(_curCell(), cursor.forceOn || cursor.a); + } + }, 500); + + /* blink attribute animation */ + setInterval(function () { + $('#screen').removeClass('blink-hide'); + setTimeout(function () { + $('#screen').addClass('blink-hide'); + }, 800); // 200 ms ON + }, 1000); + + inited = true; + } + + // constants for decoding the update blob + var SEQ_SET_COLOR_ATTR = 1; + var SEQ_REPEAT = 2; + var SEQ_SET_COLOR = 3; + var SEQ_SET_ATTR = 4; + + /** Parse received screen update object (leading S removed already) */ + function _load_content(str) { + var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell; + + if (!inited) _init(); + + var cursorMoved; + + // Set size + num = parse2B(str, i); i += 2; // height + num2 = parse2B(str, i); i += 2; // width + if (num != H || num2 != W) { + _rebuild(num, num2); + } + // console.log("Size ",num, num2); + + // Cursor position + num = parse2B(str, i); i += 2; // row + num2 = parse2B(str, i); i += 2; // col + cursorMoved = (cursor.x != num2 || cursor.y != num); + cursorSet(num, num2); + // console.log("Cursor at ",num, num2); + + // Attributes + num = parse2B(str, i); i += 2; // fg bg attribs + cursor.hidden = !(num & (1<<0)); // DEC opt "visible" + cursor.hanging = !!(num & (1<<1)); + // console.log("Attributes word ",num.toString(16)+'h'); + + Input.setAlts( + !!(num & (1<<2)), // cursors alt + !!(num & (1<<3)), // numpad alt + !!(num & (1<<4)) // fn keys alt + ); + + var mt_click = !!(num & (1<<5)); + var mt_move = !!(num & (1<<6)); + Input.setMouseMode( + mt_click, + mt_move + ); + $('#screen').toggleClass('noselect', mt_move); + + var show_buttons = !!(num & (1<<7)); + var show_config_links = !!(num & (1<<8)); + $('.x-term-conf-btn').toggleClass('hidden', !show_config_links); + $('#action-buttons').toggleClass('hidden', !show_buttons); + + fg = 7; + bg = 0; + attrs = 0; + + // Here come the content + while(i < str.length && ci> 4; + attrs = (num & 0xFF00)>>8; + } + else if (jc == SEQ_SET_COLOR) { + num = parse2B(str, i); i += 2; + fg = num & 0x0F; + bg = (num & 0xF0) >> 4; + } + else if (jc == SEQ_SET_ATTR) { + num = parse2B(str, i); i += 2; + attrs = num & 0xFF; + } + else if (jc == SEQ_REPEAT) { + num = parse2B(str, i); i += 2; + // console.log("Repeat x ",num); + for (; num>0 && ci 0 ? e(s) : " "; + x.style.opacity = s.length > 0 ? 1 : 0.2; + }); + } + + /** Audible beep for ASCII 7 */ + function _beep() { + var osc, gain; + if (!audioCtx) return; + + // Main beep + osc = audioCtx.createOscillator(); + gain = audioCtx.createGain(); + osc.connect(gain); + gain.connect(audioCtx.destination); + gain.gain.value = 0.5; + osc.frequency.value = 750; + osc.type = 'sine'; + osc.start(); + osc.stop(audioCtx.currentTime+0.05); + + // Surrogate beep (making it sound like 'oops') + osc = audioCtx.createOscillator(); + gain = audioCtx.createGain(); + osc.connect(gain); + gain.connect(audioCtx.destination); + gain.gain.value = 0.2; + osc.frequency.value = 400; + osc.type = 'sine'; + osc.start(audioCtx.currentTime+0.05); + osc.stop(audioCtx.currentTime+0.08); + } + + /** Load screen content from a binary sequence (new) */ + function load(str) { + //console.log(JSON.stringify(str)); + var content = str.substr(1); + switch(str.charAt(0)) { + case 'S': + _load_content(content); + break; + case 'T': + _load_labels(content); + break; + case 'B': + _beep(); + break; + default: + console.warn("Bad data message type, ignoring."); + console.log(str); + } + } + + return { + load: load, // full load (string) + }; +})(); +/** File upload utility */ +var TermUpl = (function() { + var lines, // array of lines without newlines + line_i, // current line index + fuTout, // timeout handle for line sending + send_delay_ms, // delay between lines (ms) + nl_str, // newline string to use + curLine, // current line (when using fuOil) + inline_pos; // Offset in line (for long lines) + + // lines longer than this are split to chunks + // sending a super-ling string through the socket is not a good idea + var MAX_LINE_LEN = 128; + + function fuOpen() { + fuStatus("Ready..."); + Modal.show('#fu_modal', onClose); + $('#fu_form').toggleClass('busy', false); + Input.blockKeys(true); + } + + function onClose() { + console.log("Upload modal closed."); + clearTimeout(fuTout); + line_i = 0; + Input.blockKeys(false); + } + + function fuStatus(msg) { + qs('#fu_prog').textContent = msg; + } + + function fuSend() { + var v = qs('#fu_text').value; + if (!v.length) { + fuClose(); + return; + } + + lines = v.split('\n'); + line_i = 0; + inline_pos = 0; // offset in line + send_delay_ms = qs('#fu_delay').value; + + // sanitize - 0 causes overflows + if (send_delay_ms < 0) { + send_delay_ms = 0; + qs('#fu_delay').value = send_delay_ms; + } + + nl_str = { + 'CR': '\r', + 'LF': '\n', + 'CRLF': '\r\n', + }[qs('#fu_crlf').value]; + + $('#fu_form').toggleClass('busy', true); + fuStatus("Starting..."); + fuSendLine(); + } + + function fuSendLine() { + if (!$('#fu_modal').hasClass('visible')) { + // Modal is closed, cancel + return; + } + + if (!Conn.canSend()) { + // postpone + fuTout = setTimeout(fuSendLine, 1); + return; + } + + if (inline_pos == 0) { + curLine = lines[line_i++] + nl_str; + } + + var chunk; + if ((curLine.length - inline_pos) <= MAX_LINE_LEN) { + chunk = curLine.substr(inline_pos, MAX_LINE_LEN); + inline_pos = 0; + } else { + chunk = curLine.substr(inline_pos, MAX_LINE_LEN); + inline_pos += MAX_LINE_LEN; + } + + if (!Input.sendString(chunk)) { + fuStatus("FAILED!"); + return; + } + + var all = lines.length; + + fuStatus(line_i+" / "+all+ " ("+(Math.round((line_i/all)*1000)/10)+"%)"); + + if (lines.length > line_i || inline_pos > 0) { + fuTout = setTimeout(fuSendLine, send_delay_ms); + } else { + closeWhenReady(); + } + } + + function closeWhenReady() { + if (!Conn.canSend()) { + // stuck in XOFF still, wait to process... + fuStatus("Waiting for Tx buffer..."); + setTimeout(closeWhenReady, 100); + } else { + fuStatus("Done."); + // delay to show it + setTimeout(function() { + fuClose(); + }, 100); + } + } + + function fuClose() { + Modal.hide('#fu_modal'); + } + + return { + init: function() { + qs('#fu_file').addEventListener('change', function (evt) { + var reader = new FileReader(); + var file = evt.target.files[0]; + console.log("Selected file type: "+file.type); + if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) { + // Deny load of blobs like img - can crash browser and will get corrupted anyway + if (!confirm("This does not look like a text file: "+file.type+"\nReally load?")) { + qs('#fu_file').value = ''; + return; + } + } + reader.onload = function(e) { + var txt = e.target.result.replace(/[\r\n]+/,'\n'); + qs('#fu_text').value = txt; + }; + console.log("Loading file..."); + reader.readAsText(file); + }, false); + }, + close: fuClose, + start: fuSend, + open: fuOpen, + } +})(); +/** Init the terminal sub-module - called from HTML */ +window.termInit = function () { + Conn.init(); + Input.init(); + TermUpl.init(); +}; diff --git a/network_set.html b/network_set.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/network_set.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/reset_screen.html b/reset_screen.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/reset_screen.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/restore_defaults.html b/restore_defaults.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/restore_defaults.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/restore_hard.html b/restore_hard.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/restore_hard.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/system_set.html b/system_set.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/system_set.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/term.html b/term.html new file mode 100644 index 0000000..981a34d --- /dev/null +++ b/term.html @@ -0,0 +1,123 @@ + + + + + + + Terminal :: ESPTerm + + + + + +
+ +
+Loading… + + + + +

+ +
+
+ +
+ +
+
+ + + + + + + +
+ + + +
+ +
+ + + diff --git a/term_set.html b/term_set.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/term_set.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wifi_connstatus.html b/wifi_connstatus.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/wifi_connstatus.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wifi_scan.html b/wifi_scan.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/wifi_scan.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wifi_set.html b/wifi_set.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/wifi_set.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/write_defaults.html b/write_defaults.html new file mode 100644 index 0000000..fbaa51d --- /dev/null +++ b/write_defaults.html @@ -0,0 +1 @@ + \ No newline at end of file