Compare commits

...

1 Commits

Author SHA1 Message Date
Christian Schwinne
59025e317e
New settings es6 (merge from pbolduc fork) (#3231)
* Initial new settings page test commit

* quick poc to add chibi style helper and start to change files to es6

* add ready and loaded functions

* added missing pageloaded variable to track if loaded already called.

* More POC to dynamically add content, minimal DOM lib based on chibi

* Add simple translation for any element that has class=l10n

* simply self executing function

---------

Co-authored-by: Phil Bolduc <philbolduc@gmail.com>
2023-06-05 16:33:51 +02:00
7 changed files with 515 additions and 0 deletions

122
tools/wled.js Normal file
View File

@ -0,0 +1,122 @@
const express = require("express");
const { createProxyMiddleware } = require('http-proxy-middleware');
const path = require("path");
const nopt = require("nopt");
const app = express();
var knownOpts = {
"help": Boolean,
"port": Number,
"settings": [path],
"host": String,
"verbose": Boolean
};
var shortHands = {
"?":["--help"],
"p":["--port"],
"s":["--settings"],
"h":["--host"],
"v":["--verbose"]
};
nopt.invalidHandler = function(k,v,t) {
// TODO: console.log(k,v,t);
}
var parsedArgs = nopt(knownOpts,shortHands,process.argv,2);
if (parsedArgs.help) {
console.log("WLED Dev Server");
console.log("Usage: wled [-v] [-?] [--settings settings.js] [--userDir DIR]");
console.log(" [--port PORT] [--host HOST]");
console.log("");
console.log("Options:");
console.log(" -p, --port PORT port to listen on");
console.log(" -s, --settings FILE use specified settings file");
console.log(" --host HOST WLED instance for dynamic content");
console.log(" -v, --verbose enable verbose output");
console.log(" -?, --help show this help");
console.log("");
process.exit();
}
// WLED_HOME - the root directory where the html files are
process.env.WLED_HOME = process.env.WLED_HOME || path.resolve(__dirname,'..','wled00','data');
parsedArgs.port = parsedArgs.port || 8080;
parsedArgs.host = parsedArgs.host || "0.0.0.0";
// get the static file reference
function static(page) {
return express.static(process.env.WLED_HOME, {index: page});
}
// add routes for each setting page
function useSettingsRoutes() {
app.use(`/settings`, static(`settings.htm`));
const settings = ['wifi', 'leds', 'ui', 'sync','time','um', 'sec'];
settings.forEach(function(setting) {
app.use(`/settings/${setting}`, static(`settings_${setting}.htm`));
});
}
// dynamic content that is proxied to real WLED instance
function useWebProxyRoutes(host) {
const httpProxy = createProxyMiddleware({ target: `http://${host}`, changeOrigin: true, logLevel: 'warn' });
const proxyRoutes = ['json', 'presets.json', 'skins.css', 'liveview'];
proxyRoutes.forEach(function(route) {
app.use(`/${route}`, httpProxy);
});
}
// proxy data to a real WLED instance
function useWebSocketProxy(host, server) {
const wsProxy = createProxyMiddleware(`ws://${host}`, { changeOrigin: true, logLevel: 'warn' });
app.use(wsProxy);
server.on('upgrade', wsProxy.upgrade);
}
// first matching route
app.use('/', static("index.htm"));
useSettingsRoutes(); // map the setting pages
useWebProxyRoutes(parsedArgs.host);
// use static files like style sheets etc
app.use(express.static(process.env.WLED_HOME));
const server = app.listen(parsedArgs.port, () => {
if (parsedArgs.verbose) {
console.log(`WLED UI listening at http://localhost:${parsedArgs.port}`)
}
});
useWebSocketProxy(parsedArgs.host, server);
var stopping = false;
function exitWhenStopped() {
if (!stopping) {
stopping = true;
server.close(function() {
console.log('WLED UI closed');
process.exit();
});
}
}
process.on('uncaughtException',function(err) {
util.log('[WLED] Uncaught Exception:');
if (err.stack) {
console.log(err.stack);
} else {
console.log(err);
}
process.exit(1);
});
process.on('SIGINT', exitWhenStopped);
process.on('SIGTERM', exitWhenStopped);
process.on('SIGHUP', exitWhenStopped);
process.on('SIGUSR2', exitWhenStopped); // for nodemon restart
process.on('SIGBREAK', exitWhenStopped); // for windows ctrl-break

129
wled00/data/cfg.css Normal file
View File

@ -0,0 +1,129 @@
@font-face {
font-family: "CIcons";
src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
}
:root {
--c-1: #111;
--c-f: #fff;
--c-2: #222;
--c-3: #333;
--c-4: #444;
--c-5: #555;
--c-6: #666;
--c-8: #888;
--c-b: #bbb;
--c-c: #ccc;
--c-e: #eee;
--c-d: #ddd;
--c-r: #831;
}
html {
touch-action: manipulation;
}
body {
margin: 0;
background-color: var(--c-2);
font-family: Helvetica, Verdana, sans-serif;
font-size: 17px;
color: var(--c-f);
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
scrollbar-width: 6px;
scrollbar-color: var(--c-sb) transparent;
}
html,
body {
height: 100%;
width: 100%;
position: fixed;
overscroll-behavior: none;
}
.icons {
font-family: "CIcons";
font-style: normal;
font-size: 24px;
line-height: 1;
display: inline-block;
}
#header {
width: 100%;
background-color: var(--c-3);
height: 45px;
}
#menu {
height: calc(100% - 45px);
background-color: var(--c-3);
width: 180px;
}
.entry {
height: 45px;
color: var(--c-b);
}
.entry:hover {
color: var(--c-f);
background-color: var(--c-4);
}
.e-icon {
padding: 10px;
display: inline-block;
width: 45px;
height: 100%;
box-sizing: border-box;
}
.e-label {
display: inline-block;
height: 45px;
vertical-align: top;
padding: 14px 0;
box-sizing: border-box;
}
.btn {
padding: 9px;
color: var(--c-b);
box-sizing: border-box;
}
.btn:hover {
color: var(--c-f);
background-color: var(--c-4);
}
.save {
float: right;
height: 100%;
}
.b-icon {
display: inline-block;
}
.b-label {
display: inline-block;
vertical-align: top;
padding: 4px;
}
@media (max-width: 500px) {
#menu {
width: 45px;
}
.e-label {
display: none;
}
}

71
wled00/data/cfg.htm Normal file
View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta charset="utf-8">
<meta name="theme-color" content="#222222">
<link rel="stylesheet" href="cfg.css">
<script src="cfg_lang.js"></script>
</head>
<body>
<div id="header">
<div class="e-icon"><i class="icons">&#xe90a;</i></div>
<div class="l e-label l10n" id="h-cfg"></div>
<div class="btn save">
<div class="b-icon"><i class="icons">&#xe905;</i></div>
<div class="l b-label l10n" id="b-save">Save</div>
</div>
</div>
<div id="menu">
<div class="entry"><div class="e-icon"><i class="icons">&#xe90c;</i></div><div class="l e-label l10n" id="e-nw">Network</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90a;</i></div><div class="l e-label l10n" id="e-hw">Hardware</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe904;</i></div><div class="l e-label l10n" id="e-ui">Customization</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe906;</i></div><div class="l e-label l10n" id="e-if">Interfaces</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90b;</i></div><div class="l e-label l10n" id="e-tm">Schedules</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe90a;</i></div><div class="l e-label l10n" id="e-dx">DMX Out</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe903;</i></div><div class="l e-label l10n" id="e-sr">Sound Reactive</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe907;</i></div><div class="l e-label l10n" id="e-um">Usermods</div></div>
<div class="entry"><div class="e-icon"><i class="icons">&#xe909;</i></div><div class="l e-label l10n" id="e-ab">About</div></div>
</div>
<div id="content">
<!-- simulate sections for each menu item -->
<div id="nw">
<h2 class="l10n">Network</h2>
</div>
<div id="hw">
<h2 class="l10n">Hardware</h2>
</div>
<div id="ui">
<h2 class="l10n">Customization</h2>
</div>
<div id="if">
<h2 class="l10n">Interfaces</h2>
</div>
<div id="tm">
<h2 class="l10n">Schedules</h2>
</div>
<div id="sr">
<h2 class="l10n">Sound Reactive</h2>
</div>
<div id="um">
<h2 class="l10n">Usermods</h2>
</div>
<div id="dx">
<h2 class="l10n">DMX Out</h2>
</div>
<div id="ab">
<h2 class="l10n">About</h2>
</div>
<div id="up">
<h2 class="l10n">Update</h2>
</div>
<div id="rb">
<h2 class="l10n">Reboot</h2>
</div>
</div>
<script src="cfg.js" type="module"></script>
</body>
</html>

30
wled00/data/cfg.js Normal file
View File

@ -0,0 +1,30 @@
import $ from './dom.mjs'
import translate from './translator.mjs'
/* Dynamically create the menu
const menuItems = [
{ "icon": "&#xe90c;", "id": "e-nw", "text": "Network" },
{ "icon": "&#xe90a;", "id": "e-hw", "text": "Hardware" },
{ "icon": "&#xe904;", "id": "e-ui", "text": "Customization" },
{ "icon": "&#xe906;", "id": "e-if", "text": "Interfaces" },
{ "icon": "&#xe90b;", "id": "e-tm", "text": "Schedules" },
{ "icon": "&#xe90a;", "id": "e-dx", "text": "DMX Out" },
{ "icon": "&#xe903;", "id": "e-sr", "text": "Sound Reactive" },
{ "icon": "&#xe907;", "id": "e-um", "text": "Usermods" },
{ "icon": "&#xe909;", "id": "e-ab", "text": "About" }
];
$().ready(function() {
const menu = $('#menu');
menuItems.map(item =>
menu.append(`<div class="entry"><div class="e-icon"><i class="icons">${item.icon}</i></div><div class="l e-label l10n" id="${item.id}">${item.text}</div></div>`)
);
});
*/
// populate labels when to dom is ready but before it is rendered
$().ready(function() {
// https://www.w3.org/International/questions/qa-i18n
// Localization is sometimes written in English as l10n
translate('.l10n');
});

10
wled00/data/cfg_lang.js Normal file
View File

@ -0,0 +1,10 @@
(function() {
// self executing function to ensure translations is set on page load
// so we dont have to wait for fetch/xhr request
window.translations = {
"About": "Über",
"Save": "Speichern",
"Schedules": "Zeitpläne",
"Sound Reactive": "Tonreaktiv"
};
}());

126
wled00/data/dom.mjs Normal file
View File

@ -0,0 +1,126 @@
/*
** DOM module - base on https://github.com/kylebarrow/chibi with IE hacks removed
**
*/
var readyfn = [],
loadedfn = [],
domready = false,
pageloaded = false,
d = document,
w = window;
// Fire any function calls on ready event
function fireReady() {
var i;
domready = true;
for (i = 0; i < readyfn.length; i += 1) {
readyfn[i]();
}
readyfn = [];
}
// Fire any function calls on loaded event
function fireLoaded() {
var i;
pageloaded = true;
for (i = 0; i < loadedfn.length; i += 1) {
loadedfn[i]();
}
loadedfn = [];
}
// Check DOM ready, page loaded
if (d.addEventListener) {
// Standards
d.addEventListener('DOMContentLoaded', fireReady, false);
w.addEventListener('load', fireLoaded, false);
}
// Loop through node array
function nodeLoop(fn, nodes) {
var i;
// Good idea to walk up the DOM
for (i = nodes.length - 1; i >= 0; i -= 1) {
fn(nodes[i]);
}
}
function dom(selector) {
var self, nodes = [],
json = false,
nodelist;
if (selector) {
// Element node
if (selector instanceof HTMLElement) {
nodes = [selector]; // return element as node list
} else if (selector instanceof NodeList) {
// JSON, document object or node list
json = (typeof selector.length !== 'number');
nodes = selector;
} else if (typeof selector === 'string') {
nodelist = d.querySelectorAll(selector);
nodes = Array.from(nodelist);
}
}
// Only attach nodes if not JSON
self = json ? {} : nodes;
// Public functions
// Fire on DOM ready
self.ready = function(fn) {
if (fn) {
if (domready) {
fn();
return self;
} else {
readyfn.push(fn);
}
}
};
// Fire on page loaded
self.loaded = function(fn) {
if (fn) {
if (pageloaded) {
fn();
return self;
} else {
loadedfn.push(fn);
}
}
};
// Executes a function on nodes
self.each = function(fn) {
if (typeof fn === 'function') {
nodeLoop(fn, nodes);
}
return self;
};
self.first = function() {
return dom(nodes.shift());
};
// Find last
self.last = function() {
return dom(nodes.pop());
};
// append html before end of the of the tag
self.append = function(value) {
if (value) {
nodeLoop(function(elm) {
elm.insertAdjacentHTML('beforeend', value);
}, nodes);
}
return self;
};
return self;
}
export default dom;

View File

@ -0,0 +1,27 @@
import $ from './dom.mjs'
var w = window;
function getText(elm) {
return elm.textContent; // or innerText ?
}
function setText(elm, value) {
if (value) {
elm.textContent = value; // or innerText ?
}
}
// perform simple translation
function translateElement(elm) {
const text = getText(elm);
setText(elm, w.translations[text]);
}
export default function translate(selector) {
if (w.translations) {
$(selector).each(translateElement)
} else {
console.info("no translations");
}
}