add notifications and better url handling
This commit is contained in:
53
software/pc_client/nginx/www/web_gui/css/sfc.css
Normal file
53
software/pc_client/nginx/www/web_gui/css/sfc.css
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
.notification-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
width: 100% - var(--pico-block-spacing-horizontal);
|
||||||
|
height: fit-content;
|
||||||
|
margin: var(--pico-spacing);
|
||||||
|
overflow: auto;
|
||||||
|
padding: var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);
|
||||||
|
border-radius: var(--pico-border-radius);
|
||||||
|
box-shadow: var(--pico-card-box-shadow);
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
opacity: 0.0;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification>p {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 0%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success {
|
||||||
|
background: rgb(1, 82, 52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-success>p {
|
||||||
|
color: rgb(33, 226, 153);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warn {
|
||||||
|
background: rgb(139, 79, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-warn>p {
|
||||||
|
color: rgb(254, 182, 112);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error {
|
||||||
|
background: rgb(175, 41, 29);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-error>p {
|
||||||
|
color: rgb(245, 163, 144)
|
||||||
|
}
|
||||||
@@ -15,31 +15,12 @@ of the license in the project repository or at <https://www.gnu.org/licenses/agp
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<link rel="stylesheet" href="css/pico.min.css">
|
<link rel="stylesheet" href="css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="css/sfc.css">
|
||||||
<script type="text/javascript" src="js/sfc.js"></script>
|
<script type="text/javascript" src="js/sfc.js"></script>
|
||||||
<title>Hello world!</title>
|
<title>Hello world!</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<dialog id="dialog_change_calibration">
|
|
||||||
<article>
|
|
||||||
<h2>Change calibration</h2>
|
|
||||||
<section>
|
|
||||||
<form id="form_change_calibration">
|
|
||||||
<label>
|
|
||||||
New calibration data (default 1400):
|
|
||||||
<input id="form_change_calibration_data" name="calibration" type="number"
|
|
||||||
placeholder="Calibration Value" value="1400" />
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
<footer>
|
|
||||||
<button class="secondary btn_cancel">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button class="btn_confirm">Confirm</button>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<dialog id="change_address">
|
<dialog id="change_address">
|
||||||
<article>
|
<article>
|
||||||
<h2>Change address</h2>
|
<h2>Change address</h2>
|
||||||
@@ -88,6 +69,14 @@ of the license in the project repository or at <https://www.gnu.org/licenses/agp
|
|||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
|
<div id="message_container" style="width: 100%;" class="notification-container">
|
||||||
|
<template id="notification">
|
||||||
|
<div class="notification">
|
||||||
|
<p>Notification</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>SplitFlap</strong></li>
|
<li><strong>SplitFlap</strong></li>
|
||||||
@@ -95,7 +84,7 @@ of the license in the project repository or at <https://www.gnu.org/licenses/agp
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a onclick="changeView('view_display')" class="secondary">Display</a></li>
|
<li><a onclick="changeView('view_display')" class="secondary">Display</a></li>
|
||||||
<li>
|
<li>
|
||||||
<details class="dropdown">
|
<details class="dropdown" id="menu_dropdown">
|
||||||
<summary>
|
<summary>
|
||||||
Configuration
|
Configuration
|
||||||
</summary>
|
</summary>
|
||||||
@@ -108,6 +97,7 @@ of the license in the project repository or at <https://www.gnu.org/licenses/agp
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="view_display"><!-- Main View : print -->
|
<div id="view_display"><!-- Main View : print -->
|
||||||
<h1>Display Something!</h1>
|
<h1>Display Something!</h1>
|
||||||
<form id="form_display">
|
<form id="form_display">
|
||||||
@@ -123,6 +113,16 @@ of the license in the project repository or at <https://www.gnu.org/licenses/agp
|
|||||||
<input id="form_display_y" name="y" type="y" placeholder="Position Y" value="0" />
|
<input id="form_display_y" name="y" type="y" placeholder="Position Y" value="0" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Display Mode:
|
||||||
|
<select id="form_display_mode" name="mode" required>
|
||||||
|
<option selected disabled value="">
|
||||||
|
Select display mode
|
||||||
|
</option>
|
||||||
|
<option value="true">Full Rotation</option>
|
||||||
|
<option value="false">Direct</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<input type="button" onClick="btn_display()" value="Display" />
|
<input type="button" onClick="btn_display()" value="Display" />
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
let socket;
|
||||||
|
|
||||||
|
/* After Document load, change view according to url and connect to ws server */
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const urlSection = location.href.split("#")[1];
|
||||||
|
if (urlSection) {
|
||||||
|
changeView(urlSection);
|
||||||
|
}
|
||||||
|
SFCConnect();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/* function to conenct to ws server */
|
||||||
|
function SFCConnect() {
|
||||||
// Create WebSocket connection.
|
// Create WebSocket connection.
|
||||||
let hostname = location.host;
|
let hostname = location.host;
|
||||||
if (hostname === "") {
|
if (hostname === "") {
|
||||||
@@ -5,42 +19,92 @@ if (hostname === "") {
|
|||||||
}
|
}
|
||||||
console.log(`Connecting to ws://${hostname}`);
|
console.log(`Connecting to ws://${hostname}`);
|
||||||
|
|
||||||
const socket = new WebSocket(`ws://${hostname}/manage/`);
|
socket = new WebSocket(`ws://${hostname}/manage/`);
|
||||||
|
|
||||||
// Connection opened
|
// Connection opened
|
||||||
socket.addEventListener("open", (event) => {
|
socket.addEventListener("open", (event) => {
|
||||||
|
notify("success", "connected!");
|
||||||
|
load_module_conf();
|
||||||
|
});
|
||||||
|
socket.addEventListener("close", (event) => {
|
||||||
|
notify("warn", "ws connection failed!. Reconnect in 5s");
|
||||||
|
setTimeout(() => {
|
||||||
|
SFCConnect();
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.addEventListener("message", (event) => {
|
socket.addEventListener("message", (event) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
switch (last_command) {
|
switch (last_command) {
|
||||||
case "dm_dump":
|
case "dm_dump":
|
||||||
parse_module_conf(JSON.parse(event.data));
|
parse_module_conf(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (data.ack) {
|
||||||
|
notify("success", "command sent!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* function to send command */
|
||||||
|
function SFCCommandAsync(data) {
|
||||||
|
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
||||||
|
last_command = data.command;
|
||||||
|
socket.send(JSON.stringify(data));
|
||||||
|
} else {
|
||||||
|
notify("error", "ws not connected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* utility function to format int to hex string */
|
||||||
function toAddressStr(address) {
|
function toAddressStr(address) {
|
||||||
const hexbase = address.toString(16).padStart(4, '0').toUpperCase();
|
const hexbase = address.toString(16).padStart(4, '0').toUpperCase();
|
||||||
return `0x${hexbase}`;
|
return `0x${hexbase}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send command
|
let messageCounter = 0;
|
||||||
function SFCCommandAsync(data) {
|
function notify(severity, text) {
|
||||||
last_command = data.command;
|
const template = document.querySelector("#notification");
|
||||||
socket.send(JSON.stringify(data));
|
const clone = template.content.cloneNode(true);
|
||||||
|
const tbody = document.querySelector("#message_container");
|
||||||
|
|
||||||
|
clone.querySelector("div").classList.add(`notification-${severity}`)
|
||||||
|
clone.querySelector("div").querySelector("p").innerHTML = text;
|
||||||
|
const messageDivID = `notification-dyn-${messageCounter}`
|
||||||
|
clone.querySelector("div").id = messageDivID;
|
||||||
|
messageCounter += 1;
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
//document.querySelector(`#${messageDivID}`).style.transition = "opacity 1s ease";
|
||||||
|
document.querySelector(`#${messageDivID}`).style.opacity = 0;
|
||||||
|
setTimeout(function () {
|
||||||
|
document.querySelector(`#${messageDivID}`).remove();
|
||||||
|
}, 1001);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
tbody.appendChild(clone);
|
||||||
|
document.querySelector(`#${messageDivID}`).style.opacity = 0.7;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function btn_display() {
|
function btn_display() {
|
||||||
const text = document.getElementById("form_display_str").value;
|
const text = document.getElementById("form_display_str").value;
|
||||||
const x = Number(document.getElementById("form_display_x").value);
|
const x = Number(document.getElementById("form_display_x").value);
|
||||||
const y = Number(document.getElementById("form_display_y").value);
|
const y = Number(document.getElementById("form_display_y").value);
|
||||||
|
const mode = document.getElementById("form_display_mode").value;
|
||||||
|
console.log(mode)
|
||||||
const msg = {
|
const msg = {
|
||||||
"command": "dm_print",
|
"command": "dm_print",
|
||||||
"string": text,
|
"string": text,
|
||||||
"x": x,
|
"x": x,
|
||||||
"y": y
|
"y": y,
|
||||||
|
"full": mode == "false" ? false : true
|
||||||
}
|
}
|
||||||
SFCCommandAsync(msg);
|
SFCCommandAsync(msg);
|
||||||
}
|
}
|
||||||
@@ -60,6 +124,20 @@ function btn_reset_module(address) {
|
|||||||
SFCCommandAsync(msg);
|
SFCCommandAsync(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function btn_save() {
|
||||||
|
const msg = {
|
||||||
|
"command": "dm_save",
|
||||||
|
}
|
||||||
|
SFCCommandAsync(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function btn_load() {
|
||||||
|
const msg = {
|
||||||
|
"command": "dm_load",
|
||||||
|
}
|
||||||
|
SFCCommandAsync(msg);
|
||||||
|
}
|
||||||
|
|
||||||
function changeView(id) {
|
function changeView(id) {
|
||||||
document.getElementById('view_display').style.display = 'none';
|
document.getElementById('view_display').style.display = 'none';
|
||||||
document.getElementById('view_conf_modules').style.display = 'none';
|
document.getElementById('view_conf_modules').style.display = 'none';
|
||||||
@@ -72,16 +150,24 @@ function changeView(id) {
|
|||||||
load_module_conf();
|
load_module_conf();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// append to url
|
||||||
|
//location.href = "test"
|
||||||
|
const newHref = `${location.href.split("#")[0]}#${id}`;
|
||||||
|
location.href = newHref;
|
||||||
|
|
||||||
|
// close menu
|
||||||
|
document.getElementById('menu_dropdown').removeAttribute('open');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let modules = [];
|
let modules = [];
|
||||||
|
|
||||||
function load_module_conf() {
|
function load_module_conf() {
|
||||||
|
if (socket) {
|
||||||
document.getElementById('btn_refresh').ariaBusy = 'true';
|
document.getElementById('btn_refresh').ariaBusy = 'true';
|
||||||
document.getElementById('btn_refresh').disabled = true;
|
document.getElementById('btn_refresh').disabled = true;
|
||||||
|
|
||||||
SFCCommandAsync({ "command": "dm_dump" });
|
SFCCommandAsync({ "command": "dm_dump" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_module_conf(data) {
|
function parse_module_conf(data) {
|
||||||
@@ -97,7 +183,7 @@ function parse_module_conf(data) {
|
|||||||
|
|
||||||
const clone = template.content.cloneNode(true);
|
const clone = template.content.cloneNode(true);
|
||||||
let summary = clone.querySelector("summary");
|
let summary = clone.querySelector("summary");
|
||||||
summary.textContent = `Module ID ${mod["id"]} : ${mod["status"]["device"]}`;
|
summary.textContent = `Module ${mod["id"]} : ${mod["status"]["device"]} | Addr: ${toAddressStr(mod["address"])} , Pos: (${mod["position"]["x"]}, ${mod["position"]["y"]})`;
|
||||||
switch (mod["status"]["device"]) {
|
switch (mod["status"]["device"]) {
|
||||||
case 'ONLINE':
|
case 'ONLINE':
|
||||||
summary.classList.add("pico-color-jade-500");
|
summary.classList.add("pico-color-jade-500");
|
||||||
@@ -238,16 +324,3 @@ function display_dialog_add_device() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function btn_save(){
|
|
||||||
const msg = {
|
|
||||||
"command": "dm_save",
|
|
||||||
}
|
|
||||||
SFCCommandAsync(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function btn_load(){
|
|
||||||
const msg = {
|
|
||||||
"command": "dm_load",
|
|
||||||
}
|
|
||||||
SFCCommandAsync(msg);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user