almost complete wifi page redesign

pull/30/head
Ondřej Hruška 7 years ago
parent a85582b94e
commit 856a694f0b
  1. 2
      html_orig/.gitignore
  2. 29
      html_orig/_debug_replacements.php
  3. 0
      html_orig/_env.php
  4. 3
      html_orig/_env.php.example
  5. 25
      html_orig/_pages.php
  6. 144
      html_orig/_start.php
  7. 664
      html_orig/css/app.css
  8. 64
      html_orig/index.php
  9. 27
      html_orig/js/app.js
  10. 5
      html_orig/jssrc/chibi.js
  11. 12
      html_orig/jssrc/utils.js
  12. 2
      html_orig/jssrc/wifi.js
  13. 35
      html_orig/messages/en.php
  14. 15
      html_orig/pages/_cfg_menu.php
  15. 26
      html_orig/pages/_head.php
  16. 5
      html_orig/pages/_tail.php
  17. 0
      html_orig/pages/cfg_network.php
  18. 123
      html_orig/pages/cfg_wifi.php
  19. 37
      html_orig/pages/term.php
  20. 178
      html_orig/sass/_layout.scss
  21. 19
      html_orig/sass/app.scss
  22. 41
      html_orig/sass/form/_form_elements.scss
  23. 55
      html_orig/sass/form/_form_layout.scss
  24. 2
      html_orig/sass/form/_index.scss
  25. 31
      html_orig/sass/layout/_base.scss
  26. 91
      html_orig/sass/layout/_box.scss
  27. 45
      html_orig/sass/layout/_content.scss
  28. 76
      html_orig/sass/layout/_espterm_specific_old.scss
  29. 9
      html_orig/sass/layout/_index.scss
  30. 18
      html_orig/sass/layout/_loader.scss
  31. 98
      html_orig/sass/layout/_menu.scss
  32. 6
      html_orig/sass/layout/_modal.scss
  33. 22
      html_orig/sass/layout/_outer-wrap.scss
  34. 20
      html_orig/sass/pages/_term.scss
  35. 125
      html_orig/sass/pages/_wifi.scss
  36. 68
      html_orig/sass/utils/_background-tiling.scss
  37. 3
      html_orig/sass/utils/_index.scss
  38. 34
      html_orig/sass/utils/_misc.scss
  39. 26
      html_orig/sass/utils/_pointer.scss
  40. 2
      html_orig/server.sh
  41. 154
      user/cgi_wifi.c

@ -1 +1 @@
_test_env.php
pages/_test_env.php

@ -0,0 +1,29 @@
<?php
return [
'%term_title%' => 'ESP8266 Wireless Terminal',
'%b1%' => '1',
'%b2%' => '2',
'%b3%' => '3',
'%b4%' => '4',
'%b5%' => '5',
'%screenData%' => '{
"w": 26, "h": 10,
"x": 0, "y": 0,
"cv": 1,
"screen": "70 t259"
}',
'%ap_enable%' => '1',
'%tpw%' => '60',
'%ap_channel%' => '7',
'%ap_ssid%' => 'ESP-123456',
'%ap_password%' => 'Passw0rd!',
'%ap_hidden%' => '0',
'%sta_ssid%' => 'LibraryFreeWifi',
'%sta_password%' => 'windows XP is The Best',
'%sta_active_ip%' => '',
'%sta_enable%' => '0',
];

@ -0,0 +1,3 @@
<?php
define("ESP_IP", "192.168.0.19");

@ -0,0 +1,25 @@
<?php
$pages = [];
/** Add a page */
function pg($key, $bc, $path) {
global $pages;
$pages[$key] = (object) [
'bodyclass' => $bc,
'path' => $path,
'label' => tr("menu.$key"),
];
}
pg('cfg_wifi', 'cfg', '/cfg/wifi');
pg('cfg_network', 'cfg', '/cfg/network');
pg('cfg_term', 'cfg', '/cfg/term');
pg('about', 'cfg', '/about');
pg('help', 'cfg', '/help');
pg('term', 'term', '/');
// technical
pg('wifi_set', '', '/cfg/wifi/set');
return $pages;

@ -1,144 +0,0 @@
<?php
require '_test_env.php';
$prod = defined('STDIN');
$root = $prod ? '' : ('http://' . ESP_IP);
$menu = [
'home' => [ $prod ? '/status' : '/page_status.php', 'Home' ],
'wifi' => [ $prod ? '/wifi' : '/page_wifi.php', 'WiFi config' ],
'about' => [ $prod ? '/about' : '/page_about.php', 'About' ],
];
$appname = 'Current Analyser';
function e($s) {
return htmlspecialchars($s, ENT_HTML5|ENT_QUOTES);
}
?><!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title><?= e($menu[$page][1]) ?> - <?= e($appname) ?></title>
<link href="/css/app.css" rel="stylesheet">
<script src="/js/all.js"></script>
<script>
// server root (or URL) - used for local development with remote AJAX calls
// (this needs CORS working on the target - which I added to esp-httpd)
var _root = <?= json_encode($root) ?>;
</script>
</head>
<body class="page-<?=$page?>">
<div id="outer">
<nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')"><?= e($appname) ?></div>
<?php
// generate the menu
foreach($menu as $k => $m) {
$sel = ($page == $k) ? ' class="selected"' : '';
$text = e($m[1]);
$url = e($m[0]);
echo "<a href=\"$url\"$sel>$text</a>";
}
?>
</nav>
<div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader">
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
<title>WiFi Settings - ESP8266 Remote Terminal</title>
<link rel="stylesheet" href="/css/app.css">
<script src="/js/app.js"></script>
</head>
<body class="page-wifi">
<img src="/img/loader.gif" alt="Loading…" id="loader">
<h1 onclick="location.href='/'">WiFi settings</h1>
<div class="Box" id="wificonfbox">
<table>
<tr>
<th>WiFi mode</th>
<td id="opmodebox">%WiFiMode%</td>
</tr>
<tr class="x-hide-noip x-hide-2">
<th>IP</th>
<td>%StaIP%</td>
</tr>
<tr>
<th>Switch to</th>
<td id="modeswitch"></td>
</tr>
<tr class="x-hide-1">
<th><label for="channel">AP channel</label></th>
<td>
<form action="/wifi/setchannel" method="GET">
<input name="ch" id="channel" type="number" step=1 min=1 max=14 value="%WiFiChannel%"><!--
--><input type="submit" value="Set" class="narrow btn-green x-hide-3">
</form>
</td>
</tr>
<tr class="x-hide-1">
<th><label for="channel">AP name</label></th>
<td>
<form action="/wifi/setname" method="GET">
<input name="name" type="text" value="%APName%"><!--
--><input type="submit" value="Set" class="narrow btn-green">
</form>
</td>
</tr>
<tr><td colspan=2 style="white-space: normal;">
<p>Some changes require a reboot, dropping connection. It can take a while to re-connect.</p>
<p>
<b>If you lose access</b>, hold the BOOT button for 2 seconds (the Tx LED starts blinking) to re-enable AP mode.
If that fails, hold the BOOT button for over 5 seconds (rapid Tx LED flashing) to perform a factory reset.
<p>
</td></tr>
</table>
</div>
<div class="Box" id="ap-box">
<h2>Select AP to join</h2>
<div id="ap-loader" class="x-hide-2">Scanning<span class="anim-dots">.</span></div>
<div id="ap-noscan" class="x-hide-1 x-hide-3">Can't scan in AP-only mode.</div>
<div id="ap-list" style="display:none"></div>
</div>
<nav id="botnav">
<a href="/">Terminal</a><!--
--><a href="/help">Help</a><!--
--><a href="/about">About</a>
</nav>
<div class="Modal hidden" id="psk-modal">
<div class="Dialog">
<form action="/wifi/connect" method="post" id="conn-form">
<input type="hidden" id="conn-essid" name="essid"><!--
--><label for="conn-passwd">Password:</label><!--
--><input type="password" id="conn-passwd" name="passwd"><!--
--><input type="submit" value="Connect!">
</form>
</div>
</div>
<script>
_root = window.location.host;
wifiInit({staSSID: '%StaSSID%', staIP: '%StaIP%', mode: '%WiFiModeNum%'});
</script>
</body>
</html>

@ -1,3 +1,4 @@
@charset "UTF-8";
/* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Set default font family to sans-serif.
@ -313,84 +314,6 @@ html {
*, *::after, *::before {
box-sizing: inherit; }
.hidden {
display: none !important; }
[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;
@ -414,74 +337,131 @@ a:hover {
color: #5abfff;
text-decoration: underline; }
.Box {
display: block;
max-width: 900px;
margin-top: 1rem;
padding: 0.61805rem 1rem;
border-radius: 3px;
background-color: rgba(255, 255, 255, 0.07); }
@media screen and (max-width: 544px) {
.Box {
margin-top: 0.61805rem;
padding: 0.23608rem 0.38198rem; } }
.Box p:first-child {
margin-top: 0; }
.hidden {
display: none !important; }
body {
[onclick] {
cursor: pointer; }
/* 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 {
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::before {
content: "▸";
padding-right: .5rem;
position: relative;
top: -0.1rem; }
@media screen and (max-width: 544px) {
#menu a {
display: none; } }
#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) {
body {
#content {
padding: 0.61805rem; } }
body > * {
#content > * {
margin-left: auto;
margin-right: auto; }
h1, h2 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
h1 {
text-align: center;
font-size: 2.02729em;
margin-top: 0;
margin-bottom: 1rem; }
#content h1 {
text-align: center;
font-size: 2.2807em;
margin-top: 0;
margin-bottom: 1rem; }
@media screen and (max-width: 544px) {
h1 {
font-size: 1.42383em;
#content h1 {
font-size: 1.80203em;
margin-bottom: 0.61805rem; } }
@media screen and (min-width: 545px) and (max-width: 1000px) {
h1 {
font-size: 1.80203em; } }
h2 {
font-size: 1.26563em;
margin-bottom: 0.61805rem; }
td, th {
padding: 0.38198rem;
white-space: nowrap; }
@media screen and (max-width: 544px) {
td, th {
padding: 0.23608rem; } }
tbody th {
text-align: right;
width: 130px;
color: white; }
@media screen and (max-width: 544px) {
tbody th {
width: auto; } }
tbody td input[type="text"], tbody td input[type="number"] {
width: 10em; }
@media screen and (max-width: 544px) {
tbody td input[type="text"], tbody td input[type="number"] {
width: 8em; } }
#content h2 {
font-size: 1.42383em;
margin-bottom: 0.61805rem; }
#content td, #content th {
padding: 0.38198rem; }
#content tbody th {
text-align: right;
width: 160px;
color: white; }
#loader {
position: absolute;
@ -496,20 +476,122 @@ tbody td input[type="text"], tbody td input[type="number"] {
#loader.show {
opacity: 1; }
ul > * {
padding-top: .1em;
padding-bottom: .1em; }
.Box {
display: block;
max-width: 900px;
margin-top: 1rem;
padding: 0.61805rem 1rem;
border-radius: 3px;
background-color: rgba(255, 255, 255, 0.07); }
@media screen and (max-width: 544px) {
.Box {
margin-top: 0.61805rem; } }
h1 + .Box {
margin-top: 0; }
.Box h2 {
margin-top: 0; }
.Box.wide {
width: initial;
max-width: initial; }
.Box.medium {
max-width: 1200px; }
.Box.str {
position: relative; }
.Box.str .Row.buttons {
position: absolute;
right: 1rem;
top: 2.7em;
margin: 12px auto; }
@media screen and (min-width: 545px) {
.Box.str .Row.buttons {
right: 0;
top: 0; } }
#botnav {
padding-top: 1.5em;
text-align: center; }
#botnav a {
padding: 0 0.38198rem;
text-decoration: underline; }
#botnav a, #botnav a:visited, #botnav a:link {
color: #2e4d6e; }
#botnav a:hover {
color: #5abfff; }
@media screen and (max-width: 544px) {
.Box.mobcol h2 {
position: relative;
cursor: pointer;
margin-bottom: 0 !important; }
.Box.mobcol h2::after {
position: absolute;
right: 0;
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; } }
.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; }
.NotifyMsg {
position: fixed;
bottom: 2.61792rem;
padding: 0.61805rem 1rem;
left: 50%;
-webkit-transform: translate(-50%, 0);
-moz-transform: translate(-50%, 0);
-ms-transform: translate(-50%, 0);
-o-transform: translate(-50%, 0);
transform: translate(-50%, 0);
-webkit-font-smoothing: subpixel-antialiased;
-webkit-transform: translateZ(0) scale(1, 1);
background: #37a349;
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;
@ -583,8 +665,8 @@ button, input[type=submit], .button {
input[type="number"], input[type="password"], input[type="text"], textarea, select {
border: 0 none;
border-bottom: 2px solid #214e7a;
background-color: #303030;
border-bottom: 2px solid #2972ba;
background-color: #3c3c3c;
color: white;
padding: 6px;
line-height: 1em;
@ -592,13 +674,154 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
-moz-outline: 0 none !important;
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: #2972ba; }
border-bottom-color: #2ea1f9; }
.Row.checkbox {
line-height: 27px; }
.Row.checkbox .box {
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-weight: bold;
position: absolute;
content: '×';
left: 0;
top: 0;
right: 0;
bottom: 0;
line-height: 26px;
text-align: center;
font-size: 27px;
vertical-align: middle;
display: none; }
.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; }
input[type="number"], input[type="password"], input[type="text"], textarea, select, label.select-wrap {
width: 250px; }
input[type="number"] {
width: 125px; }
form .Row {
vertical-align: middle;
margin: 12px auto;
text-align: left;
display: flex;
flex-direction: row;
align-items: center; }
form .Row:first-child {
margin-top: 0; }
form .Row:last-child {
margin-bottom: 0; }
form .Row .spacer {
width: 160px; }
@media screen and (max-width: 544px) {
form .Row .spacer {
display: none; } }
form .Row.buttons {
margin: 16px auto; }
form .Row.buttons input, form .Row.buttons .button {
margin-right: 0.61805rem; }
form .Row.centered {
justify-content: center; }
form .Row.message {
font-size: 1em;
text-shadow: 1px 1px 3px black;
text-align: center; }
form .Row.message.error {
color: crimson; }
form .Row.message.ok {
color: #0fe851; }
form .Row.separator {
padding-top: 14px;
border-top: 2px solid rgba(255, 255, 255, 0.1); }
form .Row textarea {
display: inline-block;
vertical-align: top;
min-height: 10rem;
flex-grow: 1;
resize: vertical; }
form .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; }
form .Row input[type="range"] {
width: 200px; }
@media screen and (max-width: 544px) {
form .Row {
flex-direction: column; }
form .Row.buttons, form .Row.centered, form .Row.checkbox {
flex-direction: row; }
form .Row.buttons {
justify-content: center; }
form .Row.buttons :last-child {
margin-right: 0; }
form .Row label {
padding-left: 0;
text-align: left;
width: auto; }
form .Row .checkbox-wrap {
order: 1;
text-align: left;
padding-bottom: 0;
border-radius: .4px;
width: auto; }
form .Row .checkbox-wrap + label {
width: auto; }
form .Row input[type="number"], form .Row input[type="password"], form .Row input[type="text"], form .Row textarea, form .Row input[type="range"], form .Row textarea {
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;
@ -629,6 +852,40 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
#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;
@ -637,50 +894,55 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
background: #42a6f9 !important;
cursor: default;
top: 0 !important; }
.AP .inner {
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 {
left: 0;
top: 1px; }
.AP .inner:hover {
background: white; }
.AP .inner > * {
.AP .inner > * {
padding: 0.61805rem;
white-space: nowrap;
word-wrap: normal; }
.AP-preview .wrap {
flex-direction: row;
background: #eee !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 .inner .rssi {
min-width: 2.5rem;
flex: 0 0 15%;
text-align: right; }
.AP .inner .rssi:after {
padding-left: 0.09018rem;
content: '%';
font-size: 0.88889em; }
.AP .inner .essid {
flex: 1 1 70%;
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
font-weight: bold; }
.AP .inner .auth {
flex: 0 0 15%; }
.page-term h1 {
.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 {
padding-bottom: 0; }
.AP-preview .wrap .passwd {
font-family: monospace; }
body.term h1 {
font-size: 1.80203em; }
@media screen and (max-width: 544px) {
.page-term h1 {
body.term h1 {
font-size: 1.42383em; } }
.page-term #screen {
body.term #screen {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@ -692,18 +954,18 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
padding: 6px;
display: inline-block;
border: 2px solid #3983CD; }
.page-term #screen span {
body.term #screen span {
white-space: pre;
cursor: pointer; }
.page-term #screen span:hover {
body.term #screen span:hover {
outline: 1px solid rgba(255, 255, 255, 0.4); }
@media screen and (max-width: 544px) {
.page-term #screen span:hover {
body.term #screen span:hover {
outline: 0 none; } }
.page-term #buttons {
body.term #buttons {
margin-top: 10px;
white-space: nowrap; }
.page-term #buttons button {
body.term #buttons button {
margin: 0 3px;
padding: 10px 0;
width: 18%;
@ -711,6 +973,16 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
min-width: initial;
cursor: pointer;
font-weight: bold; }
body.term #botnav {
padding-top: 1.5em;
text-align: center; }
body.term #botnav a {
padding: 0 0.38198rem;
text-decoration: underline; }
body.term #botnav a, body.term #botnav a:visited, body.term #botnav a:link {
color: #2e4d6e; }
body.term #botnav a:hover {
color: #5abfff; }
#termwrap {
text-align: center; }
@ -866,3 +1138,5 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
@media screen and (max-width: 1000px) {
.mq-normal-min {
display: none; } }
/*# sourceMappingURL=app.css.map */

@ -0,0 +1,64 @@
<?php
require '_env.php';
$prod = defined('STDIN');
define ('DEBUG', !$prod);
$root = DEBUG ? ESP_IP : '';
define ('LIVE_ROOT', $root);
define('CUR_PAGE', $_GET['page'] ?: 'term');
define('LOCALE', $_GET['locale'] ?: 'en');
$_messages = require(__DIR__ . '/messages/' . LOCALE . '.php');
$_pages = require('_pages.php');
define('APP_NAME', 'ESPTerm');
define('PAGE_TITLE', $_pages[CUR_PAGE]->label . ' :: ' . APP_NAME);
define('BODYCLASS', $_pages[CUR_PAGE]->bodyclass);
/** URL (dev or production) */
function url($name, $root=false) {
global $_pages;
if ($root) return LIVE_ROOT . $_pages[$name]->path;
if (DEBUG) return "/index.php?page=$name";
else return $_pages[$name]->path;
}
/** URL label for a button */
function label($name) {
global $_pages;
return $_pages[$name]->label;
}
function e($s) {
return htmlspecialchars($s, ENT_HTML5|ENT_QUOTES);
}
function tr($key) {
global $_messages;
return $_messages[$key] ?: ('??'.$key.'??');
}
/** Like eval, but allows <?php and ?> */
function include_str($code) {
$tmp = tmpfile();
$tmpf = stream_get_meta_data($tmp);
$tmpf = $tmpf ['uri'];
fwrite($tmp, $code);
$ret = include($tmpf);
fclose($tmp);
return $ret;
}
require 'pages/_head.php';
$_pf = 'pages/'.CUR_PAGE.'.php';
if (file_exists($_pf)) {
$f = file_get_contents($_pf);
$reps = require('_debug_replacements.php');
$str = str_replace(array_keys($reps), array_values($reps), $f);
include_str($str);
} else {
echo "404";
}
require 'pages/_tail.php';

@ -393,8 +393,9 @@
return cb;
};
// Toggle class
cb.toggleClass = function (classes) {
classHelper(classes, 'toggle', nodes);
cb.toggleClass = function (classes, set) {
var method = ((typeof set === 'undefined') ? 'toggle' : (+set ? 'add' : 'remove'));
classHelper(classes, method, nodes);
return cb;
};
// Has class
@ -795,6 +796,18 @@ String.prototype.format = function () {
return out;
};
function e(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function undef(x) {
return typeof x == 'undefined';
}
/** Module for toggling a modal overlay */
(function () {
var modal = {};
@ -1088,7 +1101,7 @@ $._loader = function(vis) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 2000);
}, 1000);
}
function onMessage(evt) {
@ -1211,7 +1224,7 @@ $._loader = function(vis) {
// clear the AP list
var $list = $('#ap-list');
// remove old APs
$('.AP').remove();
$('#ap-list .AP').remove();
$list.toggle(done);
$('#ap-loader').toggle(!done);
@ -1309,9 +1322,9 @@ $._loader = function(vis) {
}
$('#modeswitch').html([
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>&nbsp;<a class="button" href="/wifi/setmode?mode=2">AP only</a>',
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>',
'<a class="button" href="/wifi/setmode?mode=1">Client only</a>&nbsp;<a class="button" href="/wifi/setmode?mode=2">AP only</a>'
'<a class="button" href="/wifi/set?opmode=3">Client+AP</a>&nbsp;<a class="button" href="/wifi/set?opmode=2">AP only</a>',
'<a class="button" href="/wifi/set?opmode=3">Client+AP</a>',
'<a class="button" href="/wifi/set?opmode=1">Client only</a>&nbsp;<a class="button" href="/wifi/set?opmode=2">AP only</a>'
][obj.mode-1]);
};

@ -393,8 +393,9 @@
return cb;
};
// Toggle class
cb.toggleClass = function (classes) {
classHelper(classes, 'toggle', nodes);
cb.toggleClass = function (classes, set) {
var method = ((typeof set === 'undefined') ? 'toggle' : (+set ? 'add' : 'remove'));
classHelper(classes, method, nodes);
return cb;
};
// Has class

@ -93,3 +93,15 @@ String.prototype.format = function () {
return out;
};
function e(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function undef(x) {
return typeof x == 'undefined';
}

@ -20,7 +20,7 @@
// clear the AP list
var $list = $('#ap-list');
// remove old APs
$('.AP').remove();
$('#ap-list .AP').remove();
$list.toggle(done);
$('#ap-loader').toggle(!done);

@ -0,0 +1,35 @@
<?php
return [
'appname' => 'ESPTerm',
'menu.cfg_wifi' => 'WiFi Settings',
'menu.cfg_network' => 'Network Configuration',
'menu.cfg_term' => 'Terminal Settings',
'menu.about' => 'About ESPTerm',
'menu.help' => 'Help',
'menu.term' => 'Back to Terminal',
'box.ap' => 'Built-in Access Point',
'box.sta' => 'Client Mode',
'wifi.enable' => 'Enabled:',
'wifi.tpw' => 'Transmit Power:',
'wifi.ap_channel' => 'Channel:',
'wifi.ap_ssid' => 'AP SSID:',
'wifi.ap_password' => 'Password:',
'wifi.ap_hidden' => 'Hide SSID:',
'wifi.sta_info' => 'Selected Network:',
'wifi.sta_ssid' => 'Network SSID:',
'wifi.sta_password' => 'Password:',
'wifi.not_conn' => 'Not connected.',
'wifi.forget' => '',
'wifi.submit' => 'Apply!',
'enabled' => 'Enabled',
'disabled' => 'Disabled',
'yes' => 'Yes',
'no' => 'No',
];

@ -0,0 +1,15 @@
<nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')"><?= tr('appname') ?></div>
<?php
// generate the menu
foreach($_pages as $k => $page) {
if ($page->bodyclass !== 'cfg') continue;
$sel = (CUR_PAGE == $k) ? ' class="selected"' : '';
$text = $page->label;
$url = e(url($k));
echo "<a href=\"$url\"$sel>$text</a>";
}
?><a href="<?= e(url('term')) ?>"><?= tr('menu.term') ?></a>
</nav>

@ -0,0 +1,26 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title><?= PAGE_TITLE ?></title>
<link href="/css/app.css" rel="stylesheet">
<script src="/js/app.js"></script>
<script>var _root = <?= json_encode(LIVE_ROOT) ?>;</script>
</head>
<body class="<?= BODYCLASS ?>">
<div id="outer">
<?php
$cfg = false;
if ($_pages[CUR_PAGE]->bodyclass == 'cfg') {
$cfg = true;
require __DIR__ . '/_cfg_menu.php';
}
?>
<div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader">
<?php if ($cfg): ?>
<h1><?= tr('menu.' . CUR_PAGE) ?></h1>
<?php endif; ?>

@ -0,0 +1,5 @@
</div>
</div>
</body>
</html>

@ -0,0 +1,123 @@
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="POST">
<h2><?= tr('box.ap') ?></h2>
<div class="Row checkbox">
<label><?= tr('wifi.enable') ?></label><!--
--><span class="box"></span>
<input type="hidden" name="ap_enable" value="%ap_enable%">
</div>
<div class="Row">
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required>
</div>
<div class="Row">
<label for="ap_password"><?= tr('wifi.ap_password') ?></label>
<input type="text" name="ap_password" id="ap_password" value="%ap_password%">
</div>
<div class="Row">
<label for="ap_channel"><?= tr('wifi.ap_channel') ?></label>
<input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required>
</div>
<div class="Row range">
<label for="tpw">
<?= tr('wifi.tpw') ?>
<span class="display x-disp1 mq-phone"></span>
</label>
<input type="range" name="tpw" id="tpw" step=1 min=0 max=82 value="%tpw%">
<span class="display x-disp2 mq-tablet-min"></span>
</div>
<div class="Row checkbox">
<label><?= tr('wifi.ap_hidden') ?></label><!--
--><span class="box"></span>
<input type="hidden" name="ap_hidden" value="%ap_hidden%">
</div>
<div class="Row buttons">
<input type="submit" value="<?= tr('wifi.submit') ?>">
</div>
</form>
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="POST">
<h2><?= tr('box.sta') ?></h2>
<div class="Row checkbox">
<label><?= tr('wifi.enable') ?></label><!--
--><span class="box"></span>
<input type="hidden" name="sta_enable" value="%sta_enable%">
</div>
<input type="hidden" name="sta_ssid" id="sta_ssid" value="%sta_ssid%">
<input type="hidden" name="sta_password" id="sta_password" value="%sta_password%">
<div class="Row sta-info">
<label><?= tr('wifi.sta_info') ?></label>
<div class="AP-preview" id="sta-nw">
<div class="wrap">
<div class="inner">
<div class="essid"></div>
<div class="passwd"></div>
<div class="ip"></div>
</div>
<a class="forget" id="forget-sta">×</a>
</div>
</div>
</div>
<div class="Row buttons">
<input type="submit" value="<?= tr('wifi.submit') ?>">
</div>
</form>
<script>
$('.Row.checkbox').forEach(function(x) {
var inp = x.querySelector('input');
var box = x.querySelector('.box');
$(box).toggleClass('checked', inp.value);
$(x).on('click', function() {
inp.value = 1 - inp.value;
$(box).toggleClass('checked', inp.value)
});
});
$('.Box.mobcol').forEach(function(x) {
var h = x.querySelector('h2');
$(h).on('click', function() {
$(x).toggleClass('expanded');
});
});
function rangePt(inp) {
return Math.round(((inp.value / inp.max)*100)) + '%';
}
$('.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);
});
});
function wifiShowSelected(name, password, ip) {
$('#sta-nw .essid').html(e(name));
var nopw = undef(password) || password.length == 0;
$('#sta-nw .passwd').html(e(password)).toggleClass('hidden', nopw);
$('#sta-nw .ip').html(ip.length>0 ? ip : '<?=tr('wifi.not_conn')?>');
}
wifiShowSelected('%sta_ssid%', '%sta_password%', '%sta_active_ip%');
</script>

@ -0,0 +1,37 @@
<script>
// Workaround for badly loaded page
setTimeout(function() {
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
location.reload(true);
}
}, 2000);
</script>
<h1 onclick="location.href='<?= e(url('cfg_wifi_simple')) ?>'">%term_title%</h1>
<div id="termwrap">
<div id="screen"></div>
<div id="buttons">
<button data-n="1" class="btn-blue">%b1%</button><!--
--><button data-n="2" class="btn-blue">%b2%</button><!--
--><button data-n="3" class="btn-blue">%b3%</button><!--
--><button data-n="4" class="btn-blue">%b4%</button><!--
--><button data-n="5" class="btn-blue">%b5%</button>
</div>
</div>
<nav id="botnav">
<a href="<?= url('cfg_wifi_simple') ?>"><?= tr('menu.cfg_wifi') ?></a><!--
--><a href="<?= url('help') ?>">Help</a><!--
--><a href="<?= url('about') ?>">About</a>
</nav>
<script>
try {
termInit(%screenData%);
} catch(e) {
console.error("Fail, reloading...");
location.reload(true);
}
</script>

@ -1,178 +0,0 @@
html {
font-family: Arial, sans-serif;
color: #D0D0D0;
background: #131315;
}
html, body {
@include naked();
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;
}
.Box {
display: block;
max-width: 900px;
margin-top: dist(0);
padding: dist(-1) dist(0);
@include media($phone) {
margin-top: dist(-1);
padding: dist(-3) dist(-2);
}
//
//h1 + & {
// margin-top: 0;
//}
//
//h2 {
// margin-top: 0;
//}
p:first-child {
margin-top:0;
}
border-radius: 3px;
background-color: rgba(white, .07);
//&.wide {
// width: initial;
// max-width: initial;
//}
//
//&.medium {
// max-width: 1200px;
//}
//
//.Valfield {
// display: inline-block;
// min-width: 10em;
//}
}
body {
position: relative;
padding: dist(0);
@include media($phone) {
padding: dist(-1);
}
overflow-y: auto;
& > * {
margin-left: auto;
margin-right: auto;
}
}
h1,h2 {
@include noselect();
}
h1 {
text-align: center;
font-size: fsize(6);
margin-top: 0;
margin-bottom: dist(0);
@include media($phone) {
font-size: fsize(3);
margin-bottom: dist(-1);
}
@include media($tablet) {
font-size: fsize(5);
}
}
h2 {
font-size: fsize(2);
margin-bottom: dist(-1);
//&:first-child{margin-top:0}
}
td, th {
padding: dist(-2);
white-space: nowrap;
@include media($phone) {
padding: dist(-3);
}
}
tbody th {
text-align: right;
width: $form-label-w;
color: $c-form-label-fg;
@include media($phone) {
width: auto;
}
}
tbody td {
input[type="text"], input[type="number"] {
width: 10em;
@include media($phone) {
width: 8em;
}
}
}
// Loader wheel in top right corner
#loader {
position: absolute;
right: dist(1);
top: dist(1);
transition: opacity .2s;
opacity: 0;
@include media($phone) {
top: dist(0);
right: dist(0);
}
&.show {
opacity:1;
}
}
ul > * {
padding-top: .1em;
padding-bottom: .1em;
}
#botnav {
padding-top: 1.5em;
text-align: center;
a {
padding: 0 dist(-2);
text-decoration: underline;
&, &:visited, &:link {
color: #2e4d6e;
}
&:hover {
color: #5abfff;
}
}
}

@ -6,15 +6,15 @@
@import "utils";
$form-label-w: 130px;
$form-label-w: 160px;
$form-label-gap: 8px;
$form-field-w: 250px;
$c-form-label-fg: white;
$c-form-field-bg: #303030;
$c-form-field-bg: #3c3c3c;
$c-form-field-fg: white;
$c-form-highlight: #214e7a;
$c-form-highlight-a: #2972ba;
$c-form-highlight: #2972ba;
$c-form-highlight-a: #2ea1f9;
@function dist($x) {
@return modular-scale($x, 1rem, $golden);
@ -24,16 +24,7 @@ $c-form-highlight-a: #2972ba;
@return modular-scale($x, 1em, $major-second);
}
.hidden {
display: none !important;
}
[onclick] {
cursor: pointer;
}
@import "modal";
@import "layout";
@import "layout/index";
@import "form/index";
// import all our pages

@ -16,6 +16,47 @@
}
}
.Row.checkbox {
$h: 27px;
line-height: $h;
.box {
width: $h;
height: $h;
border: 1px solid #808080;
border-radius: 3px;
background: $c-form-field-bg;
display: inline-block;
position: relative;
cursor: pointer;
color: $c-form-highlight-a;
&::before {
font-weight: bold;
position: absolute;
content: '×';
left: 0; top: 0; right: 0; bottom: 0;
line-height: $h - 1px;
text-align: center;
font-size: $h;
vertical-align: middle;
display: none;
}
&.checked::before {
display: block;
}
}
}
.Row.range {
.display {
margin-left: 1ex;
}
label .display { font-weight: normal; }
}
//#{$all-text-inputs} {
// @include can-select();
//}

@ -5,13 +5,18 @@ form { @include naked(); }
width: $form-field-w;
}
input[type="number"] {
width: $form-field-w/2;
}
form .Row {
vertical-align: middle;
margin: 14px auto;
margin: 12px auto;
text-align: left;
display: flex;
flex-direction: row;
align-items: center;
&:first-child {
margin-top: 0;
@ -30,6 +35,7 @@ form .Row {
}
&.buttons {
margin: 16px auto;
input, .button {
margin-right: dist(-1);
}
@ -80,34 +86,39 @@ form .Row {
align-self: flex-start;
@include noselect;
@include nowrap;
}
.checkbox-wrap {
display: inline-block;
width: $form-label-w;
padding: $form-label-gap;
text-align: right;
align-self: flex-start;
input[type=checkbox] {
margin: auto;
width: auto;
height: auto;
}
& + label {
width: $form-field-w;
padding-left: 0;
text-align: left;
cursor: pointer;
}
//.checkbox-wrap {
// display: inline-block;
// width: $form-label-w;
// padding: $form-label-gap;
// text-align: right;
// align-self: flex-start;
//
// input[type=checkbox] {
// margin: auto;
// width: auto;
// height: auto;
// }
//
// & + label {
// width: $form-field-w;
// padding-left: 0;
// text-align: left;
// cursor: pointer;
// }
//}
input[type="range"] {
width: 200px;
}
// special phone style
@include media($phone) {
flex-direction: column;
&.buttons, &.centered {
&.buttons, &.centered, &.checkbox {
flex-direction: row;
}
@ -139,7 +150,7 @@ form .Row {
}
}
#{$all-text-inputs}, textarea {
#{$all-text-inputs}, input[type="range"], textarea {
width: 100%;
}
}

@ -9,5 +9,5 @@
}
}
//@import 'form_layout';
@import 'form_layout';
//@import 'select';

@ -0,0 +1,31 @@
html {
font-family: Arial, sans-serif;
color: #D0D0D0;
background: #131315;
}
html, body {
@include naked();
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;
}

@ -0,0 +1,91 @@
.Box {
display: block;
max-width: 900px;
margin-top: dist(0);
padding: dist(-1) dist(0);
@include media($phone) {
margin-top: dist(-1);
}
h1 + & {
margin-top: 0;
}
h2 {
margin-top: 0;
}
border-radius: 3px;
background-color: rgba(white, .07);
&.wide {
width: initial;
max-width: initial;
}
&.medium {
max-width: 1200px;
}
//.Valfield {
// display: inline-block;
// min-width: 10em;
//}
&.str {
position: relative;
.Row.buttons {
position: absolute;
right: dist(0);
top: 2.7em;
margin: 12px auto;
}
@include media($tablet-min) {
.Row.buttons {
//position: absolute;
right: 0;
top: 0;
//margin: 12px auto;
}
}
}
}
@include media($phone) {
.Box.mobcol {
h2 {
position: relative;
cursor: pointer;
&::after {
position: absolute;
right: 0;
content: '';
top:50%;
font-size: 120%;
font-weight: bold;
transform: translate(0,-50%) rotate(90deg);
}
margin-bottom: 0 !important;
}
&.expanded h2::after {
transform: translate(-25%,-50%) rotate(-90deg);
margin-bottom: dist(0);
}
.Row {
display: none;
}
&.expanded .Row {
display: flex;
}
}
}

@ -0,0 +1,45 @@
#content {
flex-grow: 1;
position: relative;
padding: dist(0);
@include media($phone) {
padding: dist(-1);
}
overflow-y: auto;
& > * {
margin-left: auto;
margin-right: auto;
}
h1 {
text-align: center;
font-size: fsize(7);
margin-top: 0;
margin-bottom: dist(0);
}
@include media($phone) {
h1 {
font-size: fsize(5);
margin-bottom: dist(-1);
}
}
h2 {
font-size: fsize(3);
margin-bottom: dist(-1);
}
td, th {
padding: dist(-2);
}
tbody th {
text-align: right;
width: $form-label-w;
color: $c-form-label-fg;
}
}

@ -0,0 +1,76 @@
ul > * {
padding-top: .1em;
padding-bottom: .1em;
}
h1,h2 {
@include noselect();
}
h1 {
text-align: center;
font-size: fsize(6);
margin-top: 0;
margin-bottom: dist(0);
@include media($phone) {
font-size: fsize(3);
margin-bottom: dist(-1);
}
@include media($tablet) {
font-size: fsize(5);
}
}
h2 {
font-size: fsize(2);
margin-bottom: dist(-1);
//&:first-child{margin-top:0}
}
td, th {
padding: dist(-2);
white-space: nowrap;
@include media($phone) {
padding: dist(-3);
}
}
tbody th {
text-align: right;
width: $form-label-w;
color: $c-form-label-fg;
@include media($phone) {
width: auto;
}
}
tbody td {
input[type="text"], input[type="number"] {
width: 10em;
@include media($phone) {
width: 8em;
}
}
}
body {
position: relative;
padding: dist(0);
@include media($phone) {
padding: dist(-1);
}
overflow-y: auto;
& > * {
margin-left: auto;
margin-right: auto;
}
}

@ -0,0 +1,9 @@
@import "base";
@import "outer-wrap";
@import "menu";
@import "content";
@import "loader";
@import "box";
@import "modal";

@ -0,0 +1,18 @@
// Loader wheel in top right corner
#loader {
position: absolute;
right: dist(1);
top: dist(1);
transition: opacity .2s;
opacity: 0;
@include media($phone) {
top: dist(0);
right: dist(0);
}
&.show {
opacity:1;
}
}

@ -0,0 +1,98 @@
#menu {
$menu-bg: #3983CD;
$menu-hl: #5badff; //#1bd886;
$menu-fg: white;
flex: 0 0 15rem;
background: $menu-bg;
& > * {
display: block;
text-decoration: none;
padding: dist(-1) dist(0);
@include nowrap;
@include noselect;
}
#brand {
color: $menu-fg;
background: darken($menu-bg, 10%);
font-size: 120%;
text-align: center;
position:relative;
transition: none;
font-weight: bold;
margin-bottom: dist(0);
@include media($phone) {
background: $menu-bg;
cursor: pointer;
margin-bottom: dist(-2);
&::after {
position: absolute;
color: rgba(black, .4);
right: dist(0);
content: '';
top:50%;
font-size: 120%;
font-weight: bold;
transform: translate(0,-50%) rotate(90deg);
}
}
}
&.expanded #brand {
background: darken($menu-bg, 10%);
@include media($phone) {
&:after { transform: translate(-25%,-50%) rotate(-90deg) }
}
}
a {
font-size: 130%;
color: $menu-fg;
transition: background-color 0.2s;
text-shadow: 0 0 5px rgba(black, .4);
&:hover, &.selected {
background: $menu-hl;
text-shadow: 0 0 5px rgba(black, .6);
}
&.selected {
position: relative;
box-shadow: 0 0 5px rgba(black, .5);
}
&::before {
content: "";
padding-right: .5rem;
position: relative;
top: -0.1rem;
}
@include media($phone) {
display: none;
}
}
&.expanded a { display:block }
@include media($tablet) {
#brand {
font-size: 95%;
margin-bottom: dist(-1);
}
a { font-size: 105%; }
flex-basis: 10rem;
& > * { padding: dist(-2) dist(-1); }
}
}

@ -25,8 +25,8 @@
//min-height: 15rem;
background: #1c1c1e;
border-left: 6px solid $c-form-highlight-a;
border-right: 6px solid $c-form-highlight-a;
border-left: 6px solid $c-form-highlight;
border-right: 6px solid $c-form-highlight;
box-shadow: 0 0 2px 0 #434349, 0 0 6px 0 black;
border-radius: 6px;
@ -40,7 +40,6 @@
}
}
/*
// "toast"
.NotifyMsg {
position: fixed;
@ -75,4 +74,3 @@
&.visible { opacity: 1 }
&.hidden { display: none }
}
*/

@ -0,0 +1,22 @@
/* 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;
}
@include media($phone) {
#outer {
display: block;
overflow-y: scroll;
}
}

@ -1,4 +1,4 @@
.page-term {
body.term {
h1 {
font-size: fsize(5);
@include media($phone) {
@ -42,6 +42,24 @@
font-weight: bold;
}
}
#botnav {
padding-top: 1.5em;
text-align: center;
a {
padding: 0 dist(-2);
text-decoration: underline;
&, &:visited, &:link {
color: #2e4d6e;
}
&:hover {
color: #5abfff;
}
}
}
}
#termwrap {

@ -43,6 +43,50 @@
@extend %form-row-spacing;
}
%ap-inner {
cursor: pointer;
@include noselect;
position: relative;
&:active {
left: 0;
top: 1px;
}
border-radius: 3px;
color: #222;
background: #afafaf;
transition: background-color 0.5s;
&:hover { background: white }
display: flex;
.rssi {
min-width: 2.5rem;
flex: 0 0 15%;
text-align: right;
&:after {
padding-left: dist(-5);
content: '%';
font-size: fsize(-1);
}
}
.essid {
flex: 1 1 70%;
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
font-weight: bold;
}
.auth {
flex: 0 0 15%;
}
}
.AP {
// can't use margins inside a column
@ -56,53 +100,70 @@
top: 0 !important; // no click effect
}
// the actual silver box
.inner {
cursor: pointer;
@include noselect;
@extend %ap-inner;
position: relative;
&:active {
left: 0;
top: 1px;
& > * {
padding: dist(-1);
@include nowrap;
}
}
}
border-radius: 3px;
color: #222;
.AP-preview {
.wrap {
@extend %ap-inner;
background: #afafaf;
transition: background-color 0.5s;
&:hover { background: white }
flex-direction: row;
background: #eee !important; // override the hover effect #43de81
cursor: default;
top: 0 !important; // no click effect
overflow: hidden;
display: flex;
.inner {
display: flex;
flex-direction: column;
& > * {
padding: dist(-1);
@include nowrap;
& > * {
padding: dist(-1);
@include nowrap;
}
}
.forget {
align-self: stretch;
line-height: 100%;
padding: dist(-1);
border-left: 1px solid #bbb;
display: flex;
align-items: center;
.rssi {
min-width: 2.5rem;
flex: 0 0 15%;
text-align: right;
&, &:hover {
color: black;
text-decoration: none;
}
font-size: 28px;
&:hover {
background: #dc4a6a;
color: white;
border-left: 1px solid #666;
border-bottom-right-radius: 3px;
border-top-right-radius: 3px;
}
&:after {
padding-left: dist(-5);
content: '%';
font-size: fsize(-1);
&:active {
position: relative;
padding-top: calc(#{dist(-1)} + 1px);
}
}
.essid {
flex: 1 1 70%;
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
font-weight: bold;
.essid, .passwd {
padding-bottom: 0;
}
.auth {
flex: 0 0 15%;
.passwd {
font-family: monospace;
}
}
}

@ -0,0 +1,68 @@
// Utilities for background tiling
// Use a tile as background (w, h - size of time)
@mixin tile_xy($w, $h, $x, $y) {
background-position: (-$x*$w) (-$y*$h);
}
// Use a square tile as background (size - w & h of time)
@mixin tile($size, $x, $y) {
@include tile_xy($size, $size, $x, $y);
}
// Button with sprite-sheet
// A B
// B:hover B:hover
@mixin tile_btn_h($w, $h, $x) {
@include tile_xy($w, $h, $x, 0);
&:hover {
@include tile_xy($w, $h, $x, 1);
}
}
// active the same as hover
@mixin tile_btn_h_act($w, $h, $x) {
@include tile_xy($w, $h, $x, 0);
&:hover, &.active {
@include tile_xy($w, $h, $x, 1);
}
}
// Button with sprite-sheet
// A A:hover
// B B:hover
@mixin tile_btn_v($w, $h, $y) {
@include tile_xy($w, $h, 0, $y);
&:hover {
@include tile_xy($w, $h, 1, $y);
}
}
// active the same as hover
@mixin tile_btn_v_act($w, $h, $y) {
@include tile_xy($w, $h, 0, $y);
&:hover, &.active {
@include tile_xy($w, $h, 1, $y);
}
}
@mixin inset-shadow-top($w, $c) {
box-shadow: inset 0 $w ($w*2) (-$w) $c;
}
@mixin inset-shadow-bottom($w, $c) {
box-shadow: inset 0 (-$w) ($w*2) (-$w) $c;
}
@mixin inset-shadow-left($w, $c) {
box-shadow: inset $w 0 ($w*2) (-$w) $c;
}
@mixin inset-shadow-right($w, $c) {
box-shadow: inset (-$w) 0 ($w*2) (-$w) $c;
}

@ -0,0 +1,3 @@
@import "background-tiling";
@import "pointer";
@import "misc";

@ -0,0 +1,34 @@
// Add a highlight for debugging
@mixin highlight($color) {
outline: 1px solid $color;
background: rgba($color, .05);
box-shadow: 0 0 2px 2px rgba($color, .2), inset 0 0 2px 2px rgba($color, .2);
}
// Ellipsis, but for block elements
@mixin block-ellipsis($width: 100%) {
display: block;
max-width: $width;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
}
// No margins, padding, borders
@mixin naked() {
border: 0 none;
margin: 0;
padding: 0;
text-decoration: none;
}
@mixin translate($x, $y) {
@include transform(translate($x, $y));
}
// Disallow wrapping
@mixin nowrap() {
white-space: nowrap;
word-wrap: normal;
}

@ -0,0 +1,26 @@
@mixin click-through() {
pointer-events: none;
}
// Disallow text selection
@mixin noselect() {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
// Allow text selection
@mixin can-select() {
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
cursor: text;
}

@ -1,3 +1,3 @@
#!/bin/bash
xterm -e "php -S localhost:2000"
xterm -e "php -S 0.0.0.0:2000"

@ -444,14 +444,38 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
}
}
if (GET_ARG("ap_enable")) {
dbg("Enable AP: %s", buff);
int enable = atoi(buff);
if (enable) {
wificonf->opmode |= SOFTAP_MODE;
} else {
wificonf->opmode &= ~SOFTAP_MODE;
}
}
if (GET_ARG("sta_enable")) {
dbg("Enable STA: %s", buff);
int enable = atoi(buff);
if (enable) {
wificonf->opmode |= STATION_MODE;
} else {
wificonf->opmode &= ~STATION_MODE;
}
}
// ---- AP transmit power ----
if (GET_ARG("tpw")) {
dbg("Setting AP power to: %s", buff);
int tpw = atoi(buff);
if (tpw >= 0 && tpw <= 82) { // 0 actually isn't 0 but quite low. 82 is very strong
wificonf->tpw = (u8) tpw;
wifi_change_flags.ap = true;
if (wificonf->tpw != tpw) {
wificonf->tpw = (u8) tpw;
wifi_change_flags.ap = true;
}
} else {
warn("tpw %s out of allowed range 0-82.", buff);
redir_url += sprintf(redir_url, "tpw,");
@ -464,8 +488,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
info("ap_channel = %s", buff);
int channel = atoi(buff);
if (channel > 0 && channel < 15) {
wificonf->ap_channel = (u8) channel;
wifi_change_flags.ap = true;
if (wificonf->ap_channel != channel) {
wificonf->ap_channel = (u8) channel;
wifi_change_flags.ap = true;
}
} else {
warn("Bad channel value \"%s\", allowed 1-14", buff);
redir_url += sprintf(redir_url, "ap_channel,");
@ -485,9 +511,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
buff[i] = 0;
if (strlen(buff) > 0) {
info("Setting SSID to \"%s\"", buff);
strncpy_safe(wificonf->ap_ssid, buff, SSID_LEN);
wifi_change_flags.ap = true;
if (!streq(wificonf->ap_ssid, buff)) {
info("Setting SSID to \"%s\"", buff);
strncpy_safe(wificonf->ap_ssid, buff, SSID_LEN);
wifi_change_flags.ap = true;
}
} else {
warn("Bad SSID len.");
redir_url += sprintf(redir_url, "ap_ssid,");
@ -500,9 +528,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
// Users are free to use any stupid shit in ther password,
// but it may lock them out.
if (strlen(buff) == 0 || (strlen(buff) >= 8 && strlen(buff) < PASSWORD_LEN-1)) {
info("Setting AP password to \"%s\"", buff);
strncpy_safe(wificonf->ap_password, buff, PASSWORD_LEN);
wifi_change_flags.ap = true;
if (!streq(wificonf->ap_password, buff)) {
info("Setting AP password to \"%s\"", buff);
strncpy_safe(wificonf->ap_password, buff, PASSWORD_LEN);
wifi_change_flags.ap = true;
}
} else {
warn("Bad password len.");
redir_url += sprintf(redir_url, "ap_password,");
@ -514,8 +544,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
if (GET_ARG("ap_hidden")) {
dbg("AP hidden = %s", buff);
int hidden = atoi(buff);
wificonf->ap_hidden = (hidden != 0);
wifi_change_flags.ap = true;
if (hidden != wificonf->ap_hidden) {
wificonf->ap_hidden = (hidden != 0);
wifi_change_flags.ap = true;
}
}
// ---- AP DHCP server lease time ----
@ -524,8 +556,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting DHCP lease time to: %s min.", buff);
int min = atoi(buff);
if (min >= 1 && min <= 2880) {
wificonf->ap_dhcp_time = (u16) min;
wifi_change_flags.ap = true;
if (wificonf->ap_dhcp_time != min) {
wificonf->ap_dhcp_time = (u16) min;
wifi_change_flags.ap = true;
}
} else {
warn("Lease time %s out of allowed range 1-2880.", buff);
redir_url += sprintf(redir_url, "ap_dhcp_time,");
@ -534,27 +568,31 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
// ---- AP DHCP start and end IP ----
if (GET_ARG("ap_dhcp_range_start")) {
if (GET_ARG("ap_dhcp_start")) {
dbg("Setting DHCP range start IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
wificonf->ap_dhcp_range.start_ip.addr = ip;
wifi_change_flags.ap = true;
if (wificonf->ap_dhcp_range.start_ip.addr != ip) {
wificonf->ap_dhcp_range.start_ip.addr = ip;
wifi_change_flags.ap = true;
}
} else {
warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_dhcp_range_start,");
redir_url += sprintf(redir_url, "ap_dhcp_start,");
}
}
if (GET_ARG("ap_dhcp_range_end")) {
if (GET_ARG("ap_dhcp_end")) {
dbg("Setting DHCP range end IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
wificonf->ap_dhcp_range.end_ip.addr = ip;
wifi_change_flags.ap = true;
if (wificonf->ap_dhcp_range.end_ip.addr != ip) {
wificonf->ap_dhcp_range.end_ip.addr = ip;
wifi_change_flags.ap = true;
}
} else {
warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_dhcp_range_end,");
redir_url += sprintf(redir_url, "ap_dhcp_end,");
}
}
@ -564,9 +602,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting AP local IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
wificonf->ap_addr.ip.addr = ip;
wificonf->ap_addr.gw.addr = ip; // always the same, we're the router here
wifi_change_flags.ap = true;
if (wificonf->ap_addr.ip.addr != ip) {
wificonf->ap_addr.ip.addr = ip;
wificonf->ap_addr.gw.addr = ip; // always the same, we're the router here
wifi_change_flags.ap = true;
}
} else {
warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_addr_ip,");
@ -577,10 +617,12 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting AP local IP netmask to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
// ideally this should be checked to match the IP.
// Let's hope users know what they're doing
wificonf->ap_addr.netmask.addr = ip;
wifi_change_flags.ap = true;
if (wificonf->ap_addr.netmask.addr != ip) {
// ideally this should be checked to match the IP.
// Let's hope users know what they're doing
wificonf->ap_addr.netmask.addr = ip;
wifi_change_flags.ap = true;
}
} else {
warn("Bad IP mask: %s", buff);
redir_url += sprintf(redir_url, "ap_addr_mask,");
@ -590,19 +632,23 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
// ---- Station SSID (to connect to) ----
if (GET_ARG("sta_ssid")) {
// No verification needed, at worst it fails to connect
info("Setting station SSID to: \"%s\"", buff);
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
wifi_change_flags.sta = true;
if (!streq(wificonf->sta_ssid, buff)) {
// No verification needed, at worst it fails to connect
info("Setting station SSID to: \"%s\"", buff);
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
wifi_change_flags.sta = true;
}
}
// ---- Station password (empty for none is allowed) ----
if (GET_ARG("sta_password")) {
// No verification needed, at worst it fails to connect
info("Setting station password to: \"%s\"", buff);
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
wifi_change_flags.sta = true;
if (!streq(wificonf->sta_password, buff)) {
// No verification needed, at worst it fails to connect
info("Setting station password to: \"%s\"", buff);
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
wifi_change_flags.sta = true;
}
}
// ---- Station enable/disable DHCP ----
@ -611,8 +657,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
if (GET_ARG("sta_dhcp_enable")) {
dbg("DHCP enable = %s", buff);
int enable = atoi(buff);
wificonf->sta_dhcp_enable = (enable != 0);
wifi_change_flags.sta = true;
if (wificonf->sta_dhcp_enable != enable) {
wificonf->sta_dhcp_enable = (bool)enable;
wifi_change_flags.sta = true;
}
}
// ---- Station IP config (Static IP) ----
@ -621,8 +669,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting Station mode static IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
wificonf->sta_addr.ip.addr = ip;
wifi_change_flags.sta = true;
if (wificonf->sta_addr.ip.addr != ip) {
wificonf->sta_addr.ip.addr = ip;
wifi_change_flags.sta = true;
}
} else {
warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_ip,");
@ -633,8 +683,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting Station mode static IP netmask to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0 && ip != 0xFFFFFFFFUL) {
wificonf->sta_addr.netmask.addr = ip;
wifi_change_flags.sta = true;
if (wificonf->sta_addr.netmask.addr != ip) {
wificonf->sta_addr.netmask.addr = ip;
wifi_change_flags.sta = true;
}
} else {
warn("Bad IP mask: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_mask,");
@ -645,8 +697,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
dbg("Setting Station mode static IP default gateway to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
wificonf->sta_addr.gw.addr = ip;
wifi_change_flags.sta = true;
if (wificonf->sta_addr.gw.addr != ip) {
wificonf->sta_addr.gw.addr = ip;
wifi_change_flags.sta = true;
}
} else {
warn("Bad gw IP: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_gw,");
@ -700,6 +754,12 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
else if (streq(token, "opmode")) {
sprintf(buff, "%d", wificonf->opmode);
}
else if (streq(token, "sta_enable")) {
sprintf(buff, "%d", (wificonf->opmode & STATION_MODE) != 0);
}
else if (streq(token, "ap_enable")) {
sprintf(buff, "%d", (wificonf->opmode & SOFTAP_MODE) != 0);
}
else if (streq(token, "tpw")) {
sprintf(buff, "%d", wificonf->tpw);
}
@ -718,10 +778,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
else if (streq(token, "ap_dhcp_time")) {
sprintf(buff, "%d", wificonf->ap_dhcp_time);
}
else if (streq(token, "ap_dhcp_range_start")) {
else if (streq(token, "ap_dhcp_start")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr));
}
else if (streq(token, "ap_dhcp_range_end")) {
else if (streq(token, "ap_dhcp_end")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_ip.addr));
}
else if (streq(token, "ap_addr_ip")) {

Loading…
Cancel
Save