Skip to content

Commit

Permalink
web: improve style for mobile view (#393)
Browse files Browse the repository at this point in the history
* move stylecss to beautified file

* beware! remove unused code

* tabs to space

* add responsive classes

* fix td border

* add to advanced web

* enlarge checkboxes

* use common header func for confirmation html

* fix checkbox center align

* update const css code

* move css to resources folder
  • Loading branch information
duhow committed Jun 7, 2024
1 parent d085186 commit bf3400b
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 22 deletions.
4 changes: 4 additions & 0 deletions resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Resources

Any files used along with application source code, but not to be mixed with C/C++ code.
This can include CSS, JS, favicon, etc.
233 changes: 233 additions & 0 deletions resources/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
escaped by https://www.cescaper.com/
source: https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css
*/

/*
* Usage:
* Compact / minify this code with any tool
* Copy one-line string to src/WebCfgServerConstants.h as stylecss
* TODO: automate this process upon building :)
*/

:root {
--nc-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
--nc-font-mono: Consolas, monaco, 'Ubuntu Mono', 'Liberation Mono', 'Courier New', Courier, monospace;
--nc-tx-1: #000000;
--nc-tx-2: #1A1A1A;
--nc-bg-1: #FFFFFF;
--nc-bg-2: #F6F8FA;
--nc-bg-3: #E5E7EB;
--nc-lk-1: #0070F3;
--nc-lk-2: #0366D6;
--nc-lk-tx: #FFFFFF;
--nc-ac-1: #79FFE1;
--nc-ac-tx: #0C4047
}

@media (prefers-color-scheme:dark) {
:root {
--nc-tx-1: #ffffff;
--nc-tx-2: #eeeeee;
--nc-bg-1: #000000;
--nc-bg-2: #111111;
--nc-bg-3: #222222;
--nc-lk-1: #3291FF;
--nc-lk-2: #0070F3;
--nc-lk-tx: #FFFFFF;
--nc-ac-1: #7928CA;
--nc-ac-tx: #FFFFFF
}
}

* {
margin: 0;
padding: 0
}

img,
input,
option,
p,
table,
textarea,
ul {
margin-bottom: 1rem
}

button,
html,
input,
select {
font-family: var(--nc-font-sans)
}

body {
margin: 0 auto;
max-width: 750px;
padding: 2rem;
border-radius: 6px;
overflow-x: hidden;
word-break: normal;
overflow-wrap: anywhere;
background: var(--nc-bg-1);
color: var(--nc-tx-2);
font-size: 1.03rem;
line-height: 1.5
}

::selection {
background: var(--nc-ac-1);
color: var(--nc-ac-tx)
}

h1, h2, h3, h4, h5, h6 {
line-height: 1;
color: var(--nc-tx-1);
padding-top: .875rem
}

h1, h2, h3 {
color: var(--nc-tx-1);
padding-bottom: 2px;
margin-bottom: 8px;
border-bottom: 1px solid var(--nc-bg-2)
}

h4, h5, h6 {
margin-bottom: .3rem
}

h1 { font-size: 2.25rem }
h2 { font-size: 1.85rem }
h3 { font-size: 1.55rem }
h4 { font-size: 1.25rem }
h5 { font-size: 1rem }
h6 { font-size: .875rem }
a { color: var(--nc-lk-1) }
a:hover { color: var(--nc-lk-2) }
abbr { cursor: help }
abbr:hover { cursor: help }

a button,
button,
input[type=button],
input[type=reset],
input[type=submit] {
font-size: 1rem;
display: inline-block;
padding: 6px 12px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background: var(--nc-lk-1);
color: var(--nc-lk-tx);
border: 0;
border-radius: 4px;
box-sizing: border-box;
cursor: pointer;
color: var(--nc-lk-tx)
}

a button[disabled],
button[disabled],
input[type=button][disabled],
input[type=reset][disabled],
input[type=submit][disabled] {
cursor: default;
opacity: .5;
cursor: not-allowed
}

.button:focus,
.button:hover,
button:focus,
button:hover,
input[type=button]:focus,
input[type=button]:hover,
input[type=reset]:focus,
input[type=reset]:hover,
input[type=submit]:focus,
input[type=submit]:hover {
background: var(--nc-lk-2)
}

table {
border-collapse: collapse;
width: 100%
}

td, th {
border: 1px solid var(--nc-bg-3);
text-align: left;
padding: .5rem
}

th { background: var(--nc-bg-2) }
tr:nth-child(even) { background: var(--nc-bg-2) }

textarea { max-width: 100% }

input, select, textarea {
padding: 6px 12px;
margin-bottom: .5rem;
background: var(--nc-bg-2);
color: var(--nc-tx-2);
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box
}

img { max-width: 100% }

td>input {
margin-top: 0px;
margin-bottom: 0px
}

td>textarea {
margin-top: 0px;
margin-bottom: 0px
}

td>select {
margin-top: 0px;
margin-bottom: 0px
}

#tblnav td, th {
border: 0;
border-bottom: 1px solid;
}

.tdbtn {
text-align: center;
vertical-align: middle;
}

.warning {
color: #f00;
}

@media only screen and (max-width: 600px) {
.adapt td { display: block; }

.adapt input[type=text],
.adapt input[type=password],
.adapt input[type=submit],
.adapt textarea,
.adapt select { width: 100%; }

.adapt td:has(input[type=checkbox]) {
text-align: center;
}

.adapt input[type=checkbox] {
width: 1.5em;
height: 1.5em;
}

.adapt table td:first-child { border-bottom: 0; }
.adapt table td:last-child { border-top: 0; }
}
35 changes: 14 additions & 21 deletions src/WebCfgServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,7 @@ void WebCfgServer::buildCredHtml(String &response)
{
buildHtmlHeader(response);

response.concat("<form method=\"post\" action=\"savecfg\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Credentials</h3>");
response.concat("<table>");
printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, false, true);
Expand All @@ -1186,7 +1186,7 @@ void WebCfgServer::buildCredHtml(String &response)

if(_nuki != nullptr)
{
response.concat("<br><br><form method=\"post\" action=\"savecfg\">");
response.concat("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Nuki Lock PIN</h3>");
response.concat("<table>");
printInputField(response, "NUKIPIN", "PIN Code (# to clear)", "*", 20, true);
Expand All @@ -1197,7 +1197,7 @@ void WebCfgServer::buildCredHtml(String &response)

if(_nukiOpener != nullptr)
{
response.concat("<br><br><form method=\"post\" action=\"savecfg\">");
response.concat("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Nuki Opener PIN</h3>");
response.concat("<table>");
printInputField(response, "NUKIOPPIN", "PIN Code (# to clear)", "*", 20, true);
Expand All @@ -1210,7 +1210,7 @@ void WebCfgServer::buildCredHtml(String &response)
if(_nuki != nullptr)
{
response.concat("<br><br><h3>Unpair Nuki Lock</h3>");
response.concat("<form method=\"post\" action=\"/unpairlock\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"/unpairlock\">");
response.concat("<table>");
String message = "Type ";
message.concat(_confirmCode);
Expand All @@ -1223,7 +1223,7 @@ void WebCfgServer::buildCredHtml(String &response)
if(_nukiOpener != nullptr)
{
response.concat("<br><br><h3>Unpair Nuki Opener</h3>");
response.concat("<form method=\"post\" action=\"/unpairopener\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"/unpairopener\">");
response.concat("<table>");
String message = "Type ";
message.concat(_confirmCode);
Expand All @@ -1234,8 +1234,8 @@ void WebCfgServer::buildCredHtml(String &response)
}

response.concat("<br><br><h3>Factory reset Nuki Hub</h3>");
response.concat("<h4 style=\"color: #ff0000\">This will reset all settings to default and unpair Nuki Lock and/or Opener. Optionally will also reset WiFi settings and reopen WiFi manager portal.</h4>");
response.concat("<form method=\"post\" action=\"/factoryreset\">");
response.concat("<h4 class=\"warning\">This will reset all settings to default and unpair Nuki Lock and/or Opener. Optionally will also reset WiFi settings and reopen WiFi manager portal.</h4>");
response.concat("<form class=\"adapt\" method=\"post\" action=\"/factoryreset\">");
response.concat("<table>");
String message = "Type ";
message.concat(_confirmCode);
Expand Down Expand Up @@ -1301,7 +1301,7 @@ void WebCfgServer::buildOtaCompletedHtml(String &response)
void WebCfgServer::buildMqttConfigHtml(String &response)
{
buildHtmlHeader(response);
response.concat("<form method=\"post\" action=\"savecfg\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Basic MQTT and Network Configuration</h3>");
response.concat("<table>");
printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100);
Expand Down Expand Up @@ -1348,9 +1348,9 @@ void WebCfgServer::buildMqttConfigHtml(String &response)
void WebCfgServer::buildAdvancedConfigHtml(String &response)
{
buildHtmlHeader(response);
response.concat("<form method=\"post\" action=\"savecfg\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Advanced Configuration</h3>");
response.concat("<h4 style=\"color: #ff0000\">Warning: Changing these settings can lead to bootloops that might require you to erase the ESP32 and reflash nukihub using USB/serial</h4>");
response.concat("<h4 class=\"warning\">Warning: Changing these settings can lead to bootloops that might require you to erase the ESP32 and reflash nukihub using USB/serial</h4>");
response.concat("<table>");
response.concat("<tr><td>Current bootloop prevention state</td><td>");
response.concat(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled");
Expand Down Expand Up @@ -1466,7 +1466,7 @@ void WebCfgServer::buildAccLvlHtml(String &response)
if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad()))
{
printCheckBox(response, "KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), "");
printCheckBox(response, "KPCODE", "Also publish keypad codes (<span style=\"color: #ff0000\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), "");
printCheckBox(response, "KPCODE", "Also publish keypad codes (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), "");
printCheckBox(response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), "");
}
printCheckBox(response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), "");
Expand Down Expand Up @@ -1623,7 +1623,7 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
{
buildHtmlHeader(response);

response.concat("<form method=\"post\" action=\"savecfg\">");
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Basic Nuki Configuration</h3>");
response.concat("<table>");
printCheckBox(response, "LOCKENA", "Nuki Smartlock enabled", _preferences->getBool(preference_lock_enabled), "");
Expand Down Expand Up @@ -1688,17 +1688,10 @@ void WebCfgServer::buildGpioConfigHtml(String &response)
void WebCfgServer::buildConfirmHtml(String &response, const String &message, uint32_t redirectDelay)
{
String delay(redirectDelay);
String header = "<meta http-equiv=\"Refresh\" content=\"" + delay + "; url=/\" />";

response.concat("<html>\n");
response.concat("<head>\n");
response.concat("<title>Nuki Hub</title>\n");
response.concat("<meta http-equiv=\"Refresh\" content=\"");
response.concat(redirectDelay);
response.concat("; url=/\" />");
response.concat("\n</head>\n");
response.concat("<body>\n");
buildHtmlHeader(response, header);
response.concat(message);

response.concat("</body></html>");
}

Expand Down
2 changes: 1 addition & 1 deletion src/WebCfgServerConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// escaped by https://www.cescaper.com/
// source: https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css

const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047}@media (prefers-color-scheme:dark){:root{--nc-tx-1:#ffffff;--nc-tx-2:#eeeeee;--nc-bg-1:#000000;--nc-bg-2:#111111;--nc-bg-3:#222222;--nc-lk-1:#3291FF;--nc-lk-2:#0070F3;--nc-lk-tx:#FFFFFF;--nc-ac-1:#7928CA;--nc-ac-tx:#FFFFFF}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr:hover{cursor:help}blockquote{padding:1.5rem;background:var(--nc-bg-2);border-left:5px solid var(--nc-bg-3)}abbr{cursor:help}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{background:var(--nc-bg-2);border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}header>:last-child{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono)}code,kbd,pre,samp{background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details{padding:.6rem 1rem;background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px}summary{cursor:pointer;font-weight:700}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}details[open]>:last-child{margin-bottom:0}dt{font-weight:700}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}legend{padding:0.5rem}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}table caption{font-weight:700;margin-bottom:.5rem}textarea{max-width:100%}ol,ul{padding-left:2rem}li{margin-top:.4rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0px;margin-bottom:0px}td>textarea{margin-top:0px;margin-bottom:0px}td>select{margin-top:0px;margin-bottom:0px}#tblnav td,th{border:0;border-bottom:1px solid;}.tdbtn{text-align:center;vertical-align: middle;}";
const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000;--nc-tx-2:#1a1a1a;--nc-bg-1:#fff;--nc-bg-2:#f6f8fa;--nc-bg-3:#e5e7eb;--nc-lk-1:#0070f3;--nc-lk-2:#0366d6;--nc-lk-tx:#fff;--nc-ac-1:#79ffe1;--nc-ac-tx:#0c4047}@media(prefers-color-scheme:dark){:root{--nc-tx-1:#fff;--nc-tx-2:#eee;--nc-bg-1:#000;--nc-bg-2:#111;--nc-bg-3:#222;--nc-lk-1:#3291ff;--nc-lk-2:#0070f3;--nc-lk-tx:#fff;--nc-ac-1:#7928ca;--nc-ac-tx:#fff}}*{margin:0;padding:0}img,input,option,p,table,textarea,ul{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr{cursor:help}abbr:hover{cursor:help}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}textarea{max-width:100%}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0;margin-bottom:0}td>textarea{margin-top:0;margin-bottom:0}td>select{margin-top:0;margin-bottom:0}#tblnav td,th{border:0;border-bottom:1px solid}.tdbtn{text-align:center;vertical-align:middle}.warning{color:red}@media only screen and (max-width:600px){.adapt td{display:block}.adapt input[type=text],.adapt input[type=password],.adapt input[type=submit],.adapt textarea,.adapt select{width:100%}.adapt td:has(input[type=checkbox]){text-align:center}.adapt input[type=checkbox]{width:1.5em;height:1.5em}.adapt table td:first-child{border-bottom:0}.adapt table td:last-child{border-top:0}}";

// converted to char array by https://notisrac.github.io/FileToCArray/
const unsigned char favicon_32x32[] = {
Expand Down

0 comments on commit bf3400b

Please sign in to comment.