hakkoso/dist/server.js

303 lines
9.0 KiB
JavaScript

var state = {};
var localstate = {
manual: {}, recipe: 0, page: ['General', 'Dashboard'],
maxpoints: 1000
};
var menu_items = [
{name: "General",
subitems:[
{name: "Dashboard"},
{name: "Sensors & Plots"}
]
},
{name: "Recipes",
subitems:[
{name: "Recipes Editor", link: 'recipe-editor'},
{name: "New Recipe"}
]
},
{name: "Configuration",
subitems:[
{name: "Settings"}
]
}
];
function applyState(newstate) {
if (newstate !== undefined) {
state = JSON.parse(JSON.stringify(newstate));
}
// preprocess
state.manual["dismissed"] = localstate.manual["dismissed"];
let selected_recipe = null;
for (let i = 0; i < state.recipes.length; i++) {
state.recipes[i].idx = i;
if (localstate.recipe == i) selected_recipe = state.recipes[i];
state.recipes[i].selected = localstate.recipe == i;
}
state.selected_recipe = JSON.parse(JSON.stringify(selected_recipe));
for (let i = 0; i < menu_items.length; i++) {
let group_match = menu_items[i].name == localstate.page[0];
for (let sub = 0; sub < menu_items[i].subitems.length; sub++) {
let sub_match = menu_items[i].subitems[sub].name == localstate.page[1];
menu_items[i].subitems[sub].current = group_match && sub_match;
menu_items[i].subitems[sub].path = JSON.stringify([
menu_items[i].name, menu_items[i].subitems[sub].name]);
}
}
state.menu_items = menu_items;
console.log('Apply state '+JSON.stringify(state));
// apply
render_sidebar(state);
manual_modal(state.manual);
current_recipe(state.recipe);
render_load_recipe(state);
render_plot();
}
var enable_draw = true;
var plot_data = {};
function add_plot_data(newpoints, render=true) {
for (var i = 0; i < newpoints.length; ++i) {
let point = newpoints[i]
let key = point[0];
if (!(key in plot_data)) {
plot_data[key] = {
x: [],
y: [],
type: 'scatter',
name: key
}
}
plot_data[key].x.push(point[1]);
plot_data[key].y.push(point[2]);
if (plot_data[key].x.length > localstate.maxpoints) {
plot_data[key].x.shift();
plot_data[key].y.shift();
}
}
if (render) render_plot();
}
function render_plot() {
if (enable_draw && document.getElementById("data-plot") !== null) {
Plotly.newPlot('data-plot', Object.values(plot_data));
}
}
function render_sensors(sensordata) {
let html = document.getElementById("sensor-list");
if (state.sensors === undefined || html === null) return;
let template = `<div class="content">
{{#sensors}}
<h4>{{what}}</h4>
{{^data}}No sensors found{{/data}}
<ul>
{{#data}}
<li ondblclick="alert('rename')">{{0}} -- {{1}} {{unit}}</li>
{{/data}}
</ul>
{{/sensors}}
</div>`;
let data = [];
for (let i = 0, k = Object.keys(state.sensors.units); i < k.length; ++i) {
let what = k[i];
let unit = state.sensors.units[k[i]];
let sensors = Object.keys(
Object.keys(state.sensors.found).reduce(function (filtered, key) {
if (state.sensors.found[key] == what) filtered[key] = state.sensors.found[key];
return filtered;
}, {}));
let req_data = [];
for (let j = 0; j < sensordata.length; ++j) {
if (sensors.includes(sensordata[j][0])) {
req_data.push([sensordata[j][0], sensordata[j][2]]);
}
}
data.push({what: what, unit: unit, data: req_data});
}
console.log({sensors:data});
html.innerHTML = Mustache.render(template, {sensors:data});
}
function navigate(path, link) {
console.log(link);
localstate.page = path;
applyState();
}
function render_sidebar(data) {
let html = document.getElementById("sidebar");
let template = `<aside class="menu">
{{#menu_items}}
<p class="menu-label">{{name}}</p>
<ul class="menu-list">
{{#subitems}}
<li><a onclick="navigate({{path}}, {{link}})"
class="{{#current}}is-active{{/current}}">{{name}}</a></li>
{{/subitems}}
</ul>
{{/menu_items}}
</aside>`
html.innerHTML = Mustache.render(template, data);
}
function respond_manual(response) {
socket.emit('manual response', response);
}
function select_recipe(idx) {
localstate.recipe = idx;
applyState();
}
function load_recipe() {
socket.emit('load recipe idx', localstate.recipe);
}
function dismiss_modal() {
applyState(state);
}
function render_load_recipe(data) {
let html = document.getElementById("load-recipe");
if (html === null) return;
let template = `<div class="card">
<header class="card-header">
<p class="card-header-title">
Load Recipe
</p>
</header>
<div class="card-content">
<div class="tabs is-centered">
<ul>
{{#recipes}}
<li class="{{#selected}}is-active{{/selected}}">
<a onclick="select_recipe({{idx}})">{{name}}</a>
</li>
{{/recipes}}
</ul>
</div>
{{#selected_recipe}}
{{description}}
{{#phases}}
<br><br>
<div class="content"><h4>{{name}}</h4></div>
{{text}}<br>
Ranges: FIXME<br>
On Load: {{onload}}<br>
Exit When: {{nextcond}}<br>
On Exit: {{onexit}}<br>
{{/phases}}
{{/selected_recipe}}
</div>
<footer class="card-footer">
<a onclick="load_recipe()"
class="card-footer-item is-primary">Load</a>
<a class="card-footer-item">Edit</a>
</footer>
</div>`
html.innerHTML = Mustache.render(template, data);
}
function current_recipe(data) {
let html = document.getElementById("current-recipe");
if (html === null) return;
if (data === null) {
html.innerHTML = '';
return;
}
let template = `
<div class="card">
<header class="card-header">
<p class="card-header-title">
Active Recipe: {{name}}
</p>
</header>
<div class="card-content">
{{description}}
<div class="tabs is-centered">
<ul>
{{#phases}}
<li class="{{#current}}is-active{{/current}}"><a>{{name}}</a></li>
{{/phases}}
</ul>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item is-primary">Pause</a>
<a class="card-footer-item">Stop</a>
</footer>
</div>`;
html.innerHTML = Mustache.render(template, data);
}
function manual_modal(data) {
let modal = document.getElementById("manual-modal");
let template = `<div class="modal {{^dismissed}}{{#required}}is-active{{/required}}{{/dismissed}}">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Manual Intervention Required</p>
<button class="delete" aria-label="close" onclick="dismiss_modal()"></button>
</header>
<section class="modal-card-body">
{{label}}
</section>
<footer class="modal-card-foot">
{{#options}}
<button class="button" onclick="respond_manual('{{value}}')">
{{tag}}
</button>
{{/options}}
</footer>
</div>
</div>`;
modal.innerHTML = Mustache.render(template, data);
}
let items = document.querySelectorAll('#items-list > li')
items.forEach(item => {
$(item).prop('draggable', true)
item.addEventListener('dragstart', dragStart)
item.addEventListener('drop', dropped)
item.addEventListener('dragenter', cancelDefault)
item.addEventListener('dragover', cancelDefault)
})
function dragStart (e) {
var index = $(e.target).index()
e.dataTransfer.setData('text/plain', index)
}
function dropped (e) {
cancelDefault(e)
// get new and old index
let oldIndex = e.dataTransfer.getData('text/plain')
let target = $(e.target)
let newIndex = target.index()
// remove dropped items at old place
let dropped = $(this).parent().children().eq(oldIndex).remove()
// insert the dropped items at new place
if (newIndex < oldIndex) {
target.before(dropped)
} else {
target.after(dropped)
}
}
function cancelDefault (e) {
e.preventDefault()
e.stopPropagation()
return false
}