diff --git a/build_web.sh b/build_web.sh index 734fd12..b86d192 100755 --- a/build_web.sh +++ b/build_web.sh @@ -6,6 +6,7 @@ echo "-- Preparing WWW files --" DD=html_orig/jssrc cat $DD/chibi.js \ $DD/utils.js \ + $DD/modal.js \ $DD/appcommon.js \ $DD/term.js \ $DD/wifi.js > html/js/app.js @@ -14,5 +15,6 @@ cat $DD/chibi.js \ cp html_orig/css/app.css html/css/app.css cp html_orig/term.html html/term.tpl cp html_orig/wifi.html html/wifi.tpl +cp html_orig/wifi_conn.html html/wifi_conn.tpl cp html_orig/img/loader.gif html/img/loader.gif cp html_orig/favicon.ico html/favicon.ico diff --git a/html/css/app.css b/html/css/app.css index 11d0084..5e0927c 100644 --- a/html/css/app.css +++ b/html/css/app.css @@ -319,6 +319,78 @@ html { [onclick] { cursor: pointer; } +.Modal { + 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; } + +.Dialog { + margin: 0.61805rem; + padding: 1rem; + overflow: hidden; + max-width: 100%; + max-height: 100%; + flex: 0 1 30rem; + background: #1c1c1e; + border-left: 6px solid #2972ba; + border-right: 6px solid #2972ba; + box-shadow: 0 0 2px 0 #434349, 0 0 6px 0 black; + border-radius: 6px; } + .Dialog h1, .Dialog h2 { + margin-top: 0; } + .Dialog p:last-child { + margin-bottom: 0; } + +/* +// "toast" +.NotifyMsg { + position: fixed; + bottom: dist(2); + padding: dist(-1) dist(0); + + // center horizontally + left: 50%; + @include translate(-50%,0); + // hack to remove blur in chrome + -webkit-font-smoothing: subpixel-antialiased; + -webkit-transform: translateZ(0) scale(1.0, 1.0); + + background: #37a349; + &.error { + background: #d03e42; + } + + color: white; + text-shadow: 0 0 2px black; + box-shadow: 0 0 6px 0 rgba(black, .6); + border-radius: 5px; + + max-width: 80%; + + @include media($phone) { + width: calc(100% - 1rem); + } + + transition: opacity .5s; + opacity: 0; + &.visible { opacity: 1 } + &.hidden { display: none } +} +*/ html { font-family: Arial, sans-serif; color: #D0D0D0; @@ -353,6 +425,8 @@ a:hover { .Box { margin-top: 0.61805rem; padding: 0.23608rem 0.38198rem; } } + .Box p:first-child { + margin-top: 0; } body { position: relative; @@ -376,6 +450,11 @@ body { @media screen and (min-width: 545px) and (max-width: 1000px) { body h1 { font-size: 1.80203em; } } + body h2 { + font-size: 1.26563em; + margin-bottom: 0.61805rem; } + body h2:first-child { + margin-top: 0; } body td, body th { padding: 0.38198rem; white-space: nowrap; } @@ -408,7 +487,7 @@ body { #loader.show { opacity: 1; } -button, input[type=submit] { +button, input[type=submit], .button { text-align: center; cursor: pointer; display: inline-block; @@ -428,22 +507,22 @@ button, input[type=submit] { background-color: #3983cd; box-shadow: 0 3px 0 #265f98; text-decoration: none !important; } - button:active, input[type=submit]:active { + button:active, input[type=submit]:active, .button:active { position: relative; top: 2px; } - button.narrow, input[type=submit].narrow { + button.narrow, input[type=submit].narrow, .button.narrow { min-width: initial; } - button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited { + 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, 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, 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, input[type=submit]:active, .button:active { box-shadow: 0 1px 0 #154c80; } -button, input[type=submit] { +button, input[type=submit], .button { text-align: center; cursor: pointer; display: inline-block; @@ -463,19 +542,19 @@ button, input[type=submit] { background-color: #3983cd; box-shadow: 0 3px 0 #265f98; text-decoration: none !important; } - button:active, input[type=submit]:active { + button:active, input[type=submit]:active, .button:active { position: relative; top: 2px; } - button.narrow, input[type=submit].narrow { + button.narrow, input[type=submit].narrow, .button.narrow { min-width: initial; } - button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited { + 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, 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, 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, input[type=submit]:active, .button:active { box-shadow: 0 1px 0 #154c80; } input[type="number"], input[type="password"], input[type="text"], textarea, select { @@ -491,6 +570,11 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele 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: #2972ba; } +#psk-modal form > *, #wificonfbox form > * { + margin-right: 0.38198rem; } + #psk-modal form > *:last-child, #wificonfbox form > *:last-child { + margin-right: 0; } + #ap-list { column-count: 3; column-gap: 0; @@ -502,15 +586,25 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele #ap-list { column-count: 1; } } -#ap-loader { +#ap-loader, #ap-noscan { background: rgba(255, 255, 255, 0.1); border-radius: 5px; padding: 0.38198rem; margin-bottom: 0.38198rem; } +#ap-noscan { + font-weight: bold; } + #ap-box { padding-bottom: 0.38198rem; } +#psk-modal form { + display: flex; + align-items: center; + margin: 0.38198rem; } + #psk-modal form input[type=password] { + min-width: 5rem; } + .AP { break-inside: avoid-column; max-width: 500px; @@ -569,7 +663,10 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele white-space: pre; cursor: pointer; } .page-term #screen span:hover { - outline: 1px solid rgba(255, 255, 255, 0.5); } + outline: 1px solid rgba(255, 255, 255, 0.4); } + @media screen and (max-width: 1000px) { + .page-term #screen span:hover { + outline: 0 none; } } .page-term #buttons { margin-top: 10px; white-space: nowrap; } diff --git a/html/js/app.js b/html/js/app.js index fb910e2..aae60c2 100644 --- a/html/js/app.js +++ b/html/js/app.js @@ -170,18 +170,35 @@ classarray = classes.split(/\s+/); nodeLoop(function (elm) { for (i = 0; i < classarray.length; i += 1) { - search = new RegExp('\\b' + classarray[i] + '\\b', 'g'); - replace = new RegExp(' *' + classarray[i] + '\\b', 'g'); + var clz = classarray[i]; 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)) { + 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); } @@ -362,11 +379,12 @@ }; // Add class cb.addClass = function (classes) { - if (classes) { - nodeLoop(function (elm) { - elm.className += ' ' + classes; - }, nodes); - } + classHelper(classes, 'add', nodes); + // if (classes) { + // nodeLoop(function (elm) { + // elm.className += ' ' + classes; + // }, nodes); + // } return cb; }; // Remove class @@ -777,6 +795,47 @@ String.prototype.format = function () { return out; }; +/** Module for toggling a modal overlay */ +(function () { + var modal = {}; + + modal.show = function (sel) { + var $m = $(sel); + $m.removeClass('hidden visible'); + setTimeout(function () { + $m.addClass('visible'); + }, 1); + }; + + modal.hide = function (sel) { + var $m = $(sel); + $m.removeClass('visible'); + setTimeout(function () { + $m.addClass('hidden'); + }, 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; +})(); /** Global generic init */ $.ready(function () { // loader dots... @@ -789,6 +848,7 @@ $.ready(function () { }); }, 1000); + // flipping number boxes with the mouse wheel $('input[type=number]').on('mousewheel', function(e) { var val = +$(this).val(); if (isNaN(val)) val = 1; @@ -816,10 +876,11 @@ $.ready(function () { e.preventDefault(); }); + + Modal.init(); }); $._loader = function(vis) { - console.log("loader fn", vis); if(vis) $('#loader').addClass('show'); else @@ -1202,23 +1263,17 @@ $._loader = function(vis) { $item.on('click', function () { var $th = $(this); - var ssid = $th.data('ssid'); - var pass = ''; + // populate the form + $('#conn-essid').val($th.data('ssid')); + $('#conn-passwd').val(''); // clear if ($th.data('pwd')) { // this AP needs a password - pass = prompt("Password for \""+ssid+"\":"); - if (pass === null) { - return; - } + Modal.show('#psk-modal'); + } else { + Modal.show('#reset-modal'); + $('#conn-form').submit(); } - - $.post('http://'+_root+'/wifi/connect', null, { - data: { - essid: ssid, - passwd: pass - } - }); }); @@ -1229,7 +1284,7 @@ $._loader = function(vis) { /** Ask the CGI what APs are visible (async) */ function scanAPs() { - $.get('http://'+_root+'/wifi/scan', onScan); + $.get('/wifi/scan', onScan); } function rescan(time) { @@ -1248,8 +1303,85 @@ $._loader = function(vis) { // } //}; - curSSID = obj.curSSID; + // Hide what should be hidden in this mode + $('.x-hide-'+obj.mode).addClass('hidden'); + obj.mode = +obj.mode; + + // Channel writable only in AP mode + if (obj.mode != 2) $('#channel').attr('readonly', 1); + + curSSID = obj.staSSID; + + // add SSID to the opmode field + if (curSSID) { + var box = $('#opmodebox'); + box.html(box.html() + ' (' + curSSID + ')'); + } + + // hide IP if IP not received + if (!obj.staIP) $('.x-hide-noip').addClass('hidden'); + + // scan if not AP + if (obj.mode != 2) { + scanAPs(); + } + + $('#modeswitch').html([ + 'Client+AP AP only', + 'Client+AP', + 'Client only AP only' + ][obj.mode-1]); + }; + + window.wifiConn = function () { + var xhr = new XMLHttpRequest(); + var abortTmeo; + + function getStatus() { + xhr.open("GET", "/wifi/connstatus"); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) { + clearTimeout(abortTmeo); + var data = JSON.parse(xhr.responseText); + var done = false; + var msg = '...'; + + if (data.status == "idle") { + msg = "Preparing to connect"; + } + else if (data.status == "success") { + msg = "Connected! Received IP " + data.ip + "."; + done = true; + } + else if (data.status == "working") { + msg = "Connecting to selected AP"; + } + else if (data.status == "fail") { + msg = "Connection failed, check your password and try again."; + done = true; + } + + $("#status").html(msg); + + if (done) { + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + } else { + window.setTimeout(getStatus, 1000); + } + } + }; + + abortTmeo = setTimeout(function () { + xhr.abort(); + $("#status").html("Telemetry lost, try reconnecting to the AP."); + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + }, 4000); + + xhr.send(); + } - scanAPs(); + getStatus(); }; })(); diff --git a/html/wifi.tpl b/html/wifi.tpl index 60a4669..6cc9cef 100644 --- a/html/wifi.tpl +++ b/html/wifi.tpl @@ -15,46 +15,69 @@

WiFi settings

-
+
- - + + - - - + + + - + + + + + - - + + +
WiFi mode:%WiFiMode%WiFi mode%WiFiMode%
%WiFiapwarn%
IP%StaIP%
Switch to
-   - +
-   - +
+

Some changes require a reboot, dropping connection. It can take a while to re-connect.

+

+ If you lose access, connect GPIO0 to GND for 5 seconds to enter Client+AP mode. + If that fails, try the UART factory reset command "\e]FR\a". +

+

Select AP to join

-
Scanning.
+
Scanning.
+
Can't scan in AP-only mode.
+ + diff --git a/html/wifi_conn.tpl b/html/wifi_conn.tpl new file mode 100755 index 0000000..a9448dc --- /dev/null +++ b/html/wifi_conn.tpl @@ -0,0 +1,26 @@ + + + + + + + Connecting… + + + + + + +

Connecting to network

+ +
+

Status:
.

+ +
+ + + + diff --git a/html_orig/css/app.css b/html_orig/css/app.css index 11d0084..5e0927c 100644 --- a/html_orig/css/app.css +++ b/html_orig/css/app.css @@ -319,6 +319,78 @@ html { [onclick] { cursor: pointer; } +.Modal { + 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; } + +.Dialog { + margin: 0.61805rem; + padding: 1rem; + overflow: hidden; + max-width: 100%; + max-height: 100%; + flex: 0 1 30rem; + background: #1c1c1e; + border-left: 6px solid #2972ba; + border-right: 6px solid #2972ba; + box-shadow: 0 0 2px 0 #434349, 0 0 6px 0 black; + border-radius: 6px; } + .Dialog h1, .Dialog h2 { + margin-top: 0; } + .Dialog p:last-child { + margin-bottom: 0; } + +/* +// "toast" +.NotifyMsg { + position: fixed; + bottom: dist(2); + padding: dist(-1) dist(0); + + // center horizontally + left: 50%; + @include translate(-50%,0); + // hack to remove blur in chrome + -webkit-font-smoothing: subpixel-antialiased; + -webkit-transform: translateZ(0) scale(1.0, 1.0); + + background: #37a349; + &.error { + background: #d03e42; + } + + color: white; + text-shadow: 0 0 2px black; + box-shadow: 0 0 6px 0 rgba(black, .6); + border-radius: 5px; + + max-width: 80%; + + @include media($phone) { + width: calc(100% - 1rem); + } + + transition: opacity .5s; + opacity: 0; + &.visible { opacity: 1 } + &.hidden { display: none } +} +*/ html { font-family: Arial, sans-serif; color: #D0D0D0; @@ -353,6 +425,8 @@ a:hover { .Box { margin-top: 0.61805rem; padding: 0.23608rem 0.38198rem; } } + .Box p:first-child { + margin-top: 0; } body { position: relative; @@ -376,6 +450,11 @@ body { @media screen and (min-width: 545px) and (max-width: 1000px) { body h1 { font-size: 1.80203em; } } + body h2 { + font-size: 1.26563em; + margin-bottom: 0.61805rem; } + body h2:first-child { + margin-top: 0; } body td, body th { padding: 0.38198rem; white-space: nowrap; } @@ -408,7 +487,7 @@ body { #loader.show { opacity: 1; } -button, input[type=submit] { +button, input[type=submit], .button { text-align: center; cursor: pointer; display: inline-block; @@ -428,22 +507,22 @@ button, input[type=submit] { background-color: #3983cd; box-shadow: 0 3px 0 #265f98; text-decoration: none !important; } - button:active, input[type=submit]:active { + button:active, input[type=submit]:active, .button:active { position: relative; top: 2px; } - button.narrow, input[type=submit].narrow { + button.narrow, input[type=submit].narrow, .button.narrow { min-width: initial; } - button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited { + 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, 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, 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, input[type=submit]:active, .button:active { box-shadow: 0 1px 0 #154c80; } -button, input[type=submit] { +button, input[type=submit], .button { text-align: center; cursor: pointer; display: inline-block; @@ -463,19 +542,19 @@ button, input[type=submit] { background-color: #3983cd; box-shadow: 0 3px 0 #265f98; text-decoration: none !important; } - button:active, input[type=submit]:active { + button:active, input[type=submit]:active, .button:active { position: relative; top: 2px; } - button.narrow, input[type=submit].narrow { + button.narrow, input[type=submit].narrow, .button.narrow { min-width: initial; } - button, button:link, button:visited, input[type=submit], input[type=submit]:link, input[type=submit]:visited { + 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, 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, 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, input[type=submit]:active, .button:active { box-shadow: 0 1px 0 #154c80; } input[type="number"], input[type="password"], input[type="text"], textarea, select { @@ -491,6 +570,11 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele 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: #2972ba; } +#psk-modal form > *, #wificonfbox form > * { + margin-right: 0.38198rem; } + #psk-modal form > *:last-child, #wificonfbox form > *:last-child { + margin-right: 0; } + #ap-list { column-count: 3; column-gap: 0; @@ -502,15 +586,25 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele #ap-list { column-count: 1; } } -#ap-loader { +#ap-loader, #ap-noscan { background: rgba(255, 255, 255, 0.1); border-radius: 5px; padding: 0.38198rem; margin-bottom: 0.38198rem; } +#ap-noscan { + font-weight: bold; } + #ap-box { padding-bottom: 0.38198rem; } +#psk-modal form { + display: flex; + align-items: center; + margin: 0.38198rem; } + #psk-modal form input[type=password] { + min-width: 5rem; } + .AP { break-inside: avoid-column; max-width: 500px; @@ -569,7 +663,10 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele white-space: pre; cursor: pointer; } .page-term #screen span:hover { - outline: 1px solid rgba(255, 255, 255, 0.5); } + outline: 1px solid rgba(255, 255, 255, 0.4); } + @media screen and (max-width: 1000px) { + .page-term #screen span:hover { + outline: 0 none; } } .page-term #buttons { margin-top: 10px; white-space: nowrap; } diff --git a/html_orig/js/app.js b/html_orig/js/app.js index 73c3ee2..aae60c2 100644 --- a/html_orig/js/app.js +++ b/html_orig/js/app.js @@ -170,18 +170,35 @@ classarray = classes.split(/\s+/); nodeLoop(function (elm) { for (i = 0; i < classarray.length; i += 1) { - search = new RegExp('\\b' + classarray[i] + '\\b', 'g'); - replace = new RegExp(' *' + classarray[i] + '\\b', 'g'); + var clz = classarray[i]; 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)) { + 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); } @@ -362,11 +379,12 @@ }; // Add class cb.addClass = function (classes) { - if (classes) { - nodeLoop(function (elm) { - elm.className += ' ' + classes; - }, nodes); - } + classHelper(classes, 'add', nodes); + // if (classes) { + // nodeLoop(function (elm) { + // elm.className += ' ' + classes; + // }, nodes); + // } return cb; }; // Remove class @@ -574,9 +592,6 @@ query = serializeData(opts.data); } - console.log(opts); - console.log(query); - if (query && (method === 'GET')) { url += (url.indexOf('?') === -1) ? '?' + query : '&' + query; query = null; @@ -780,6 +795,47 @@ String.prototype.format = function () { return out; }; +/** Module for toggling a modal overlay */ +(function () { + var modal = {}; + + modal.show = function (sel) { + var $m = $(sel); + $m.removeClass('hidden visible'); + setTimeout(function () { + $m.addClass('visible'); + }, 1); + }; + + modal.hide = function (sel) { + var $m = $(sel); + $m.removeClass('visible'); + setTimeout(function () { + $m.addClass('hidden'); + }, 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; +})(); /** Global generic init */ $.ready(function () { // loader dots... @@ -792,6 +848,7 @@ $.ready(function () { }); }, 1000); + // flipping number boxes with the mouse wheel $('input[type=number]').on('mousewheel', function(e) { var val = +$(this).val(); if (isNaN(val)) val = 1; @@ -819,10 +876,11 @@ $.ready(function () { e.preventDefault(); }); + + Modal.init(); }); $._loader = function(vis) { - console.log("loader fn", vis); if(vis) $('#loader').addClass('show'); else @@ -1205,21 +1263,17 @@ $._loader = function(vis) { $item.on('click', function () { var $th = $(this); - var ssid = $th.data('ssid'); - var pass = ''; + // populate the form + $('#conn-essid').val($th.data('ssid')); + $('#conn-passwd').val(''); // clear if ($th.data('pwd')) { // this AP needs a password - var pass = prompt("Password for \""+ssid+"\":"); - if (pass === null) { - return; - } + Modal.show('#psk-modal'); + } else { + Modal.show('#reset-modal'); + $('#conn-form').submit(); } - - $.post('http://'+_root+'/wifi/connect', null, { - essid: ssid, - passwd: pass - }); }); @@ -1230,7 +1284,7 @@ $._loader = function(vis) { /** Ask the CGI what APs are visible (async) */ function scanAPs() { - $.get('http://'+_root+'/wifi/scan', onScan); + $.get('/wifi/scan', onScan); } function rescan(time) { @@ -1249,8 +1303,85 @@ $._loader = function(vis) { // } //}; - curSSID = obj.curSSID; + // Hide what should be hidden in this mode + $('.x-hide-'+obj.mode).addClass('hidden'); + obj.mode = +obj.mode; + + // Channel writable only in AP mode + if (obj.mode != 2) $('#channel').attr('readonly', 1); + + curSSID = obj.staSSID; + + // add SSID to the opmode field + if (curSSID) { + var box = $('#opmodebox'); + box.html(box.html() + ' (' + curSSID + ')'); + } + + // hide IP if IP not received + if (!obj.staIP) $('.x-hide-noip').addClass('hidden'); + + // scan if not AP + if (obj.mode != 2) { + scanAPs(); + } + + $('#modeswitch').html([ + 'Client+AP AP only', + 'Client+AP', + 'Client only AP only' + ][obj.mode-1]); + }; + + window.wifiConn = function () { + var xhr = new XMLHttpRequest(); + var abortTmeo; + + function getStatus() { + xhr.open("GET", "/wifi/connstatus"); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) { + clearTimeout(abortTmeo); + var data = JSON.parse(xhr.responseText); + var done = false; + var msg = '...'; + + if (data.status == "idle") { + msg = "Preparing to connect"; + } + else if (data.status == "success") { + msg = "Connected! Received IP " + data.ip + "."; + done = true; + } + else if (data.status == "working") { + msg = "Connecting to selected AP"; + } + else if (data.status == "fail") { + msg = "Connection failed, check your password and try again."; + done = true; + } + + $("#status").html(msg); + + if (done) { + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + } else { + window.setTimeout(getStatus, 1000); + } + } + }; + + abortTmeo = setTimeout(function () { + xhr.abort(); + $("#status").html("Telemetry lost, try reconnecting to the AP."); + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + }, 4000); + + xhr.send(); + } - scanAPs(); + getStatus(); }; })(); diff --git a/html_orig/jssrc/appcommon.js b/html_orig/jssrc/appcommon.js index 8f30aa5..09fd0b1 100644 --- a/html_orig/jssrc/appcommon.js +++ b/html_orig/jssrc/appcommon.js @@ -10,6 +10,7 @@ $.ready(function () { }); }, 1000); + // flipping number boxes with the mouse wheel $('input[type=number]').on('mousewheel', function(e) { var val = +$(this).val(); if (isNaN(val)) val = 1; @@ -37,10 +38,11 @@ $.ready(function () { e.preventDefault(); }); + + Modal.init(); }); $._loader = function(vis) { - console.log("loader fn", vis); if(vis) $('#loader').addClass('show'); else diff --git a/html_orig/jssrc/chibi.js b/html_orig/jssrc/chibi.js index 6709282..63dfa77 100755 --- a/html_orig/jssrc/chibi.js +++ b/html_orig/jssrc/chibi.js @@ -170,18 +170,35 @@ classarray = classes.split(/\s+/); nodeLoop(function (elm) { for (i = 0; i < classarray.length; i += 1) { - search = new RegExp('\\b' + classarray[i] + '\\b', 'g'); - replace = new RegExp(' *' + classarray[i] + '\\b', 'g'); + var clz = classarray[i]; 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)) { + 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); } @@ -362,11 +379,12 @@ }; // Add class cb.addClass = function (classes) { - if (classes) { - nodeLoop(function (elm) { - elm.className += ' ' + classes; - }, nodes); - } + classHelper(classes, 'add', nodes); + // if (classes) { + // nodeLoop(function (elm) { + // elm.className += ' ' + classes; + // }, nodes); + // } return cb; }; // Remove class diff --git a/html_orig/jssrc/modal.js b/html_orig/jssrc/modal.js new file mode 100644 index 0000000..b6326f4 --- /dev/null +++ b/html_orig/jssrc/modal.js @@ -0,0 +1,41 @@ +/** Module for toggling a modal overlay */ +(function () { + var modal = {}; + + modal.show = function (sel) { + var $m = $(sel); + $m.removeClass('hidden visible'); + setTimeout(function () { + $m.addClass('visible'); + }, 1); + }; + + modal.hide = function (sel) { + var $m = $(sel); + $m.removeClass('visible'); + setTimeout(function () { + $m.addClass('hidden'); + }, 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; +})(); diff --git a/html_orig/jssrc/wifi.js b/html_orig/jssrc/wifi.js index 0095da5..8d0ff4c 100644 --- a/html_orig/jssrc/wifi.js +++ b/html_orig/jssrc/wifi.js @@ -54,23 +54,17 @@ $item.on('click', function () { var $th = $(this); - var ssid = $th.data('ssid'); - var pass = ''; + // populate the form + $('#conn-essid').val($th.data('ssid')); + $('#conn-passwd').val(''); // clear if ($th.data('pwd')) { // this AP needs a password - pass = prompt("Password for \""+ssid+"\":"); - if (pass === null) { - return; - } + Modal.show('#psk-modal'); + } else { + Modal.show('#reset-modal'); + $('#conn-form').submit(); } - - $.post('http://'+_root+'/wifi/connect', null, { - data: { - essid: ssid, - passwd: pass - } - }); }); @@ -81,7 +75,7 @@ /** Ask the CGI what APs are visible (async) */ function scanAPs() { - $.get('http://'+_root+'/wifi/scan', onScan); + $.get('/wifi/scan', onScan); } function rescan(time) { @@ -100,8 +94,85 @@ // } //}; - curSSID = obj.curSSID; + // Hide what should be hidden in this mode + $('.x-hide-'+obj.mode).addClass('hidden'); + obj.mode = +obj.mode; + + // Channel writable only in AP mode + if (obj.mode != 2) $('#channel').attr('readonly', 1); + + curSSID = obj.staSSID; + + // add SSID to the opmode field + if (curSSID) { + var box = $('#opmodebox'); + box.html(box.html() + ' (' + curSSID + ')'); + } + + // hide IP if IP not received + if (!obj.staIP) $('.x-hide-noip').addClass('hidden'); + + // scan if not AP + if (obj.mode != 2) { + scanAPs(); + } + + $('#modeswitch').html([ + 'Client+AP AP only', + 'Client+AP', + 'Client only AP only' + ][obj.mode-1]); + }; + + window.wifiConn = function () { + var xhr = new XMLHttpRequest(); + var abortTmeo; + + function getStatus() { + xhr.open("GET", "/wifi/connstatus"); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) { + clearTimeout(abortTmeo); + var data = JSON.parse(xhr.responseText); + var done = false; + var msg = '...'; + + if (data.status == "idle") { + msg = "Preparing to connect"; + } + else if (data.status == "success") { + msg = "Connected! Received IP " + data.ip + "."; + done = true; + } + else if (data.status == "working") { + msg = "Connecting to selected AP"; + } + else if (data.status == "fail") { + msg = "Connection failed, check your password and try again."; + done = true; + } + + $("#status").html(msg); + + if (done) { + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + } else { + window.setTimeout(getStatus, 1000); + } + } + }; + + abortTmeo = setTimeout(function () { + xhr.abort(); + $("#status").html("Telemetry lost, try reconnecting to the AP."); + $('#backbtn').removeClass('hidden'); + $('.anim-dots').addClass('hidden'); + }, 4000); + + xhr.send(); + } - scanAPs(); + getStatus(); }; })(); diff --git a/html_orig/packjs.sh b/html_orig/packjs.sh index 91f0d7c..f00f6c1 100755 --- a/html_orig/packjs.sh +++ b/html_orig/packjs.sh @@ -2,6 +2,7 @@ cat jssrc/chibi.js \ jssrc/utils.js \ + jssrc/modal.js \ jssrc/appcommon.js \ jssrc/term.js \ jssrc/wifi.js > js/app.js diff --git a/html_orig/sass/_layout.scss b/html_orig/sass/_layout.scss index a7ba371..9326f07 100644 --- a/html_orig/sass/_layout.scss +++ b/html_orig/sass/_layout.scss @@ -42,6 +42,10 @@ a:hover { // margin-top: 0; //} + p:first-child { + margin-top:0; + } + border-radius: 3px; background-color: rgba(white, .07); @@ -91,10 +95,11 @@ body { } } - //h2 { - // font-size: fsize(3); - // margin-bottom: dist(-1); - //} + h2 { + font-size: fsize(2); + margin-bottom: dist(-1); + &:first-child{margin-top:0} + } td, th { padding: dist(-2); diff --git a/html_orig/sass/_modal.scss b/html_orig/sass/_modal.scss new file mode 100755 index 0000000..c0c72fb --- /dev/null +++ b/html_orig/sass/_modal.scss @@ -0,0 +1,78 @@ +.Modal { + 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(black, .65); + opacity: 0; + &.visible { opacity: 1 } + &.hidden { display: none } +} + +.Dialog { + margin: dist(-1); + padding: dist(0); + overflow: hidden; + + max-width: 100%; + max-height: 100%; + flex: 0 1 30rem; + //min-height: 15rem; + + background: #1c1c1e; + border-left: 6px solid $c-form-highlight-a; + border-right: 6px solid $c-form-highlight-a; + box-shadow: 0 0 2px 0 #434349, 0 0 6px 0 black; + + border-radius: 6px; + + h1,h2 { + margin-top:0; + } + + p:last-child { + margin-bottom: 0; + } +} + +/* +// "toast" +.NotifyMsg { + position: fixed; + bottom: dist(2); + padding: dist(-1) dist(0); + + // center horizontally + left: 50%; + @include translate(-50%,0); + // hack to remove blur in chrome + -webkit-font-smoothing: subpixel-antialiased; + -webkit-transform: translateZ(0) scale(1.0, 1.0); + + background: #37a349; + &.error { + background: #d03e42; + } + + color: white; + text-shadow: 0 0 2px black; + box-shadow: 0 0 6px 0 rgba(black, .6); + border-radius: 5px; + + max-width: 80%; + + @include media($phone) { + width: calc(100% - #{dist(0)}); + } + + transition: opacity .5s; + opacity: 0; + &.visible { opacity: 1 } + &.hidden { display: none } +} +*/ diff --git a/html_orig/sass/app.scss b/html_orig/sass/app.scss index 7a33230..a9b9a62 100755 --- a/html_orig/sass/app.scss +++ b/html_orig/sass/app.scss @@ -32,6 +32,7 @@ $c-form-highlight-a: #2972ba; cursor: pointer; } +@import "modal"; @import "layout"; @import "form/index"; diff --git a/html_orig/sass/form/_buttons.scss b/html_orig/sass/form/_buttons.scss index 5f9681d..7c0b5b3 100755 --- a/html_orig/sass/form/_buttons.scss +++ b/html_orig/sass/form/_buttons.scss @@ -24,7 +24,7 @@ $btn-orange-b: #dd8751; $btn-orange-fa: #FEFEFE; $btn-orange-ba: #C6733F; -button, input[type=submit] { +button, input[type=submit], .button { @include fancy-btn-base(); &.narrow { diff --git a/html_orig/sass/form/_index.scss b/html_orig/sass/form/_index.scss index 21d1ede..7736400 100755 --- a/html_orig/sass/form/_index.scss +++ b/html_orig/sass/form/_index.scss @@ -1,5 +1,13 @@ @import 'fancy_button_mixins'; @import 'buttons'; @import 'form_elements'; + +%form-row-spacing { + & > * { + margin-right: dist(-2); + &:last-child { margin-right: 0 } + } +} + //@import 'form_layout'; //@import 'select'; diff --git a/html_orig/sass/pages/_term.scss b/html_orig/sass/pages/_term.scss index 3e9893a..6b085e4 100755 --- a/html_orig/sass/pages/_term.scss +++ b/html_orig/sass/pages/_term.scss @@ -13,7 +13,10 @@ white-space: pre; cursor: pointer; &:hover { - outline: 1px solid rgba(#ffffff,0.5); + outline: 1px solid rgba(#ffffff,0.4); + @include media($tablet-max) { + outline: 0 none; + } } } } diff --git a/html_orig/sass/pages/_wifi.scss b/html_orig/sass/pages/_wifi.scss index 7e60844..78a52f1 100755 --- a/html_orig/sass/pages/_wifi.scss +++ b/html_orig/sass/pages/_wifi.scss @@ -13,17 +13,36 @@ margin: 0 (- dist(-3)); } -#ap-loader { +#ap-loader, #ap-noscan { background: rgba(white, .1); border-radius: 5px; padding: dist(-2); margin-bottom: dist(-2); } +#ap-noscan { + font-weight: bold; +} + #ap-box { padding-bottom: dist(-2); } +#psk-modal form { + @extend %form-row-spacing; + display: flex; + align-items: center; + margin: dist(-2); + + input[type=password] { + min-width: 5rem; + } +} + +#wificonfbox form { + @extend %form-row-spacing; +} + .AP { // can't use margins inside a column diff --git a/html_orig/wifi.html b/html_orig/wifi.html index 60a4669..6cc9cef 100644 --- a/html_orig/wifi.html +++ b/html_orig/wifi.html @@ -15,46 +15,69 @@

WiFi settings

-
+
- - + + - - - + + + - + + + + + - - + + +
WiFi mode:%WiFiMode%WiFi mode%WiFiMode%
%WiFiapwarn%
IP%StaIP%
Switch to
-   - +
-   - +
+

Some changes require a reboot, dropping connection. It can take a while to re-connect.

+

+ If you lose access, connect GPIO0 to GND for 5 seconds to enter Client+AP mode. + If that fails, try the UART factory reset command "\e]FR\a". +

+

Select AP to join

-
Scanning.
+
Scanning.
+
Can't scan in AP-only mode.
+ + diff --git a/html_orig/wifi1.html b/html_orig/wifi1.html deleted file mode 100644 index 92eb127..0000000 --- a/html_orig/wifi1.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - WiFi Settings - ESP8266 Remote Terminal - - - - - - -Loading… - -

WiFi settings

- -
- - - - - - - - - - - - - - - - - -
WiFi mode:%WiFiMode%
%WiFiapwarn%
-
-   - -
-
-
-   - -
-
-
- -
-

Select AP to join

-
Scanning.
- -
- - - - diff --git a/html_orig/wifi_conn.html b/html_orig/wifi_conn.html new file mode 100755 index 0000000..a9448dc --- /dev/null +++ b/html_orig/wifi_conn.html @@ -0,0 +1,26 @@ + + + + + + + Connecting… + + + + + + +

Connecting to network

+ +
+

Status:
.

+ +
+ + + + diff --git a/libesphttpd b/libesphttpd index 9ff92fa..439df03 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit 9ff92faacfd857ee6d1d9c1069dc4752e6b56589 +Subproject commit 439df03f4b8e4c1ec5d7dc14695c80a4e35eb7ce diff --git a/user/routes.c b/user/routes.c index 0d36411..9c7b3ab 100644 --- a/user/routes.c +++ b/user/routes.c @@ -23,7 +23,7 @@ static int wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, */ HttpdBuiltInUrl routes[] = { // redirect func for the captive portal - ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp-remote-term.ap"), + ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp-terminal.ap"), // --- Web pages --- ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"), @@ -45,6 +45,7 @@ HttpdBuiltInUrl routes[] = { ROUTE_CGI("/wifi/scan", cgiWiFiScan), ROUTE_CGI("/wifi/connect", cgiWiFiConnect), ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus), + ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"), ROUTE_CGI("/wifi/setmode", cgiWiFiSetMode), ROUTE_CGI("/wifi/setchannel", cgiWiFiSetChannel), ROUTE_CGI("/wifi/setname", cgiWiFiSetSSID), diff --git a/user/screen.c b/user/screen.c index c43ef59..5320d39 100644 --- a/user/screen.c +++ b/user/screen.c @@ -462,7 +462,7 @@ screen_putchar(char ch) default: if (ch < ' ') { // Discard - warn("Ignoring control char %d", (int)c); + warn("Ignoring control char %d", (int)ch); goto done; } }