2022-11-19 18:52:01 +01:00
|
|
|
var state = {};
|
|
|
|
var localstate = {
|
|
|
|
manual: {}, recipe: 0, page: ['General', 'Dashboard'],
|
2023-01-04 14:52:28 +01:00
|
|
|
maxpoints: 1000,
|
|
|
|
editing: {}
|
2022-11-19 18:52:01 +01:00
|
|
|
};
|
|
|
|
var menu_items = [
|
|
|
|
{name: "General",
|
|
|
|
subitems:[
|
|
|
|
{name: "Dashboard"},
|
|
|
|
{name: "Sensors & Plots"}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{name: "Recipes",
|
|
|
|
subitems:[
|
2023-01-04 14:52:28 +01:00
|
|
|
{name: "Recipes Editor", link: 'edit-recipe'},
|
2022-11-19 18:52:01 +01:00
|
|
|
{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
|
2023-02-21 14:00:07 +01:00
|
|
|
render_actuators(state.actuators);
|
2022-11-19 18:52:01 +01:00
|
|
|
render_sidebar(state);
|
|
|
|
manual_modal(state.manual);
|
2023-02-21 16:34:40 +01:00
|
|
|
current_recipe(Object.assign({}, state.state, state.recipe));
|
2022-11-19 18:52:01 +01:00
|
|
|
render_load_recipe(state);
|
|
|
|
render_plot();
|
2023-01-04 14:52:28 +01:00
|
|
|
localstate.editing.recipe = state.recipes.findIndex(
|
|
|
|
function (el) {return el.name == localstate.editing.recipe_name;});
|
2022-11-19 18:52:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var enable_draw = true;
|
|
|
|
var plot_data = {};
|
|
|
|
|
2023-02-21 19:23:12 +01:00
|
|
|
function toggle_update_plot() {
|
|
|
|
enable_draw = !enable_draw;
|
|
|
|
}
|
|
|
|
function pause_plotting() {enable_draw = false;}
|
|
|
|
function resume_plotting() {enable_draw = true;}
|
|
|
|
function set_plot_points(value) {
|
|
|
|
let current = localstate.maxpoints;
|
|
|
|
if (value === undefined) {
|
|
|
|
value = parseInt(document.getElementById('point-number').value);
|
|
|
|
}
|
|
|
|
localstate.maxpoints = value;
|
|
|
|
if (value <= current) {
|
|
|
|
// we alredy have all the points we want just set it and let other
|
|
|
|
// function discard extra points
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Ask for more data
|
|
|
|
socket.emit('get sensors history', value);
|
|
|
|
}
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
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]);
|
2023-02-21 19:23:12 +01:00
|
|
|
// This is different since we want maxpoints to be the absolute max, not
|
|
|
|
// for each variable, but good enough for now (or the opposite?)
|
2023-02-21 14:00:43 +01:00
|
|
|
while (plot_data[key].x.length > localstate.maxpoints) {
|
2022-11-19 18:52:01 +01:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-21 14:00:07 +01:00
|
|
|
function render_actuators(data) {
|
|
|
|
let html = document.getElementById("actuator-list");
|
|
|
|
if (data === undefined || html === null) return;
|
|
|
|
let template = `<div class="content">
|
2023-02-21 19:23:12 +01:00
|
|
|
Total Consumption: {{total}}Wh<br>
|
2023-02-21 14:00:07 +01:00
|
|
|
{{#consumption}}
|
2023-02-21 19:23:12 +01:00
|
|
|
{{name}}: {{Wh}}Wh</br>
|
2023-02-21 14:00:07 +01:00
|
|
|
{{/consumption}}
|
|
|
|
</div>`;
|
|
|
|
let total = 0;
|
|
|
|
let out = [];
|
|
|
|
for (var key in data.consumption){
|
|
|
|
let val = data.consumption[key];
|
|
|
|
total += val;
|
2023-02-21 19:23:12 +01:00
|
|
|
out.push({name: key, Wh: val.toFixed(2)});
|
2023-02-21 14:00:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
html.innerHTML = Mustache.render(template, { total: total.toFixed(2),
|
|
|
|
consumption: out });
|
|
|
|
}
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
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});
|
|
|
|
}
|
|
|
|
html.innerHTML = Mustache.render(template, {sensors:data});
|
|
|
|
}
|
|
|
|
|
|
|
|
function navigate(path, link) {
|
2022-11-19 23:44:27 +01:00
|
|
|
if (link !== undefined) {
|
2023-01-04 14:52:28 +01:00
|
|
|
window.location.href = "/" + link
|
2022-11-19 23:44:27 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-11-19 18:52:01 +01:00
|
|
|
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}}
|
2022-11-19 23:44:27 +01:00
|
|
|
<li><a onclick="navigate({{path}}, '{{link}}')"
|
2022-11-19 18:52:01 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-01-04 14:52:28 +01:00
|
|
|
function edit_recipe(recipeid) {
|
|
|
|
let recipe = recipeid === undefined ? localstate.recipe : recipeid;
|
|
|
|
window.location.href = '/edit-recipe/'+recipe;
|
|
|
|
}
|
|
|
|
|
|
|
|
function currently_editing_recipe(name) {
|
|
|
|
localstate.editing.recipe_name = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_field(fieldname, newvalue) {
|
|
|
|
state.recipes[localstate.editing.recipe].phases[localstate.editing.phase][fieldname] = newvalue;
|
|
|
|
console.log(state.recipes[localstate.editing.recipe].phases[localstate.editing.phase]);
|
|
|
|
console.log(fieldname, newvalue);
|
|
|
|
}
|
|
|
|
|
|
|
|
function save_phase() {
|
|
|
|
let edited_phase = state.recipes[localstate.editing.recipe].phases[
|
|
|
|
localstate.editing.phase];
|
|
|
|
console.log(edited_phase);
|
|
|
|
socket.emit('update recipe phase',
|
|
|
|
localstate.editing.recipe, localstate.editing.phase, edited_phase)
|
|
|
|
}
|
|
|
|
|
|
|
|
function edit_phase(phaseid) {
|
|
|
|
console.log(localstate);
|
|
|
|
let html = document.getElementById("phase-editor");
|
|
|
|
if (html === null) return;
|
|
|
|
localstate.editing.phase = phaseid;
|
|
|
|
data = state.recipes[localstate.editing.recipe].phases[phaseid];
|
|
|
|
console.log(data.text);
|
|
|
|
let template = `<div class="field">
|
|
|
|
<label class="label">Phase Name</label>
|
|
|
|
<div class="control">
|
|
|
|
<input class="input"
|
|
|
|
oninput="update_field('name', this.value)"
|
|
|
|
type="text" placeholder="Phase Name" value="{{name}}">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
<label class="label">Phase Description</label>
|
|
|
|
<div class="control">
|
|
|
|
<textarea class="textarea" placeholder="Phase Description"
|
2023-01-06 10:54:06 +01:00
|
|
|
oninput="update_field('text', this.value)">{{text}}</textarea>
|
2023-01-04 14:52:28 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
<label class="label">On Load</label>
|
|
|
|
<div class="control">
|
|
|
|
<textarea
|
2023-01-06 10:54:06 +01:00
|
|
|
oninput="update_field('onload', this.value)"
|
|
|
|
class="textarea" placeholder="#t">{{onload}}</textarea>
|
2023-01-04 14:52:28 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field">
|
|
|
|
<label class="label">Next Condition</label>
|
|
|
|
<div class="control">
|
2023-01-06 10:54:06 +01:00
|
|
|
<textarea oninput="update_field('nextcond', this.value)"
|
|
|
|
class="textarea" placeholder="#t">{{nextcond}}</textarea>
|
2023-01-04 14:52:28 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field">
|
|
|
|
<label class="label">On Exit</label>
|
|
|
|
<div class="control">
|
2023-01-06 10:54:06 +01:00
|
|
|
<textarea oninput="update_field('onexit', this.value)"
|
|
|
|
class="textarea" placeholder="#t">{{onexit}}</textarea>
|
2023-01-04 14:52:28 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="field is-grouped">
|
|
|
|
<div class="control">
|
|
|
|
<button class="button is-link"
|
|
|
|
onclick="save_phase();">Save</button>
|
|
|
|
</div>
|
|
|
|
<div class="control">
|
|
|
|
<button class="button is-link is-light"
|
|
|
|
onclick="stop_editing_phase();">Cancel</button>
|
|
|
|
</div>
|
|
|
|
<div class="control">
|
|
|
|
<button class="button is-danger"
|
|
|
|
onclick="delete_phase();">Delete</button>
|
|
|
|
</div>
|
|
|
|
</div>`;
|
|
|
|
html.innerHTML = Mustache.render(template, data);
|
|
|
|
}
|
|
|
|
|
2022-11-19 21:42:54 +01:00
|
|
|
function stop_recipe() {
|
|
|
|
socket.emit('stop recipe');
|
|
|
|
}
|
|
|
|
|
2023-02-21 14:02:42 +01:00
|
|
|
function maybe_stop_recipe() {
|
|
|
|
question_modal({required: true, dismissed: false, label: "Stoppiamo?", options: [
|
|
|
|
{action: "stop_recipe();", tag:"Sicuro"},
|
|
|
|
{action: "alert('non interrompiamo')", tag:"No"}]});
|
|
|
|
}
|
|
|
|
|
2022-11-19 21:42:54 +01:00
|
|
|
function next_phase() {
|
|
|
|
socket.emit('next phase');
|
|
|
|
}
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
function dismiss_modal() {
|
|
|
|
applyState(state);
|
|
|
|
}
|
|
|
|
|
2023-02-21 14:02:07 +01:00
|
|
|
function reload_recipes() {
|
|
|
|
socket.emit('reload recipes');
|
|
|
|
}
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
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>
|
2023-02-21 14:01:47 +01:00
|
|
|
<button class="button is-link"
|
|
|
|
onclick="reload_recipes();">Reload config</button>
|
2022-11-19 18:52:01 +01:00
|
|
|
</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}}
|
2023-01-04 14:52:28 +01:00
|
|
|
<br/><br/>
|
2022-11-19 23:44:27 +01:00
|
|
|
Controllers:<br/>
|
|
|
|
<ul>
|
2022-11-19 20:37:13 +01:00
|
|
|
{{#controllers}}
|
2022-11-19 23:44:27 +01:00
|
|
|
<li>{{.}}</li>
|
2022-11-19 20:37:13 +01:00
|
|
|
{{/controllers}}
|
2022-11-19 23:44:27 +01:00
|
|
|
</ul>
|
2022-11-19 18:52:01 +01:00
|
|
|
{{#phases}}
|
|
|
|
<br><br>
|
|
|
|
<div class="content"><h4>{{name}}</h4></div>
|
|
|
|
{{text}}<br>
|
2023-01-06 10:54:06 +01:00
|
|
|
On Load: {{onload}}<br>
|
|
|
|
Exit When: {{nextcond}}<br>
|
|
|
|
On Exit: {{onexit}}<br>
|
2022-11-19 18:52:01 +01:00
|
|
|
{{/phases}}
|
|
|
|
{{/selected_recipe}}
|
|
|
|
</div>
|
|
|
|
<footer class="card-footer">
|
|
|
|
<a onclick="load_recipe()"
|
|
|
|
class="card-footer-item is-primary">Load</a>
|
2023-01-04 14:52:28 +01:00
|
|
|
<a onclick="edit_recipe()"
|
|
|
|
class="card-footer-item">Edit</a>
|
2022-11-19 18:52:01 +01:00
|
|
|
</footer>
|
|
|
|
</div>`
|
|
|
|
html.innerHTML = Mustache.render(template, data);
|
|
|
|
}
|
|
|
|
|
2023-02-21 16:34:40 +01:00
|
|
|
function stodate(value) {
|
|
|
|
var date = new Date(0);
|
2023-02-21 16:46:53 +01:00
|
|
|
// Workaround an incompatibility with a temporary version, you can remove this "if"
|
|
|
|
if (value !== undefined) date.setSeconds(value);
|
2023-02-21 16:34:40 +01:00
|
|
|
date.setSeconds(value);
|
|
|
|
var timeString = date.toISOString().substring(11, 19);
|
|
|
|
return timeString;
|
|
|
|
}
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
function current_recipe(data) {
|
|
|
|
let html = document.getElementById("current-recipe");
|
|
|
|
if (html === null) return;
|
2023-02-21 16:34:40 +01:00
|
|
|
if (data === null || data.phases === undefined) {
|
2022-11-19 18:52:01 +01:00
|
|
|
html.innerHTML = '';
|
|
|
|
return;
|
|
|
|
}
|
2022-11-19 21:42:54 +01:00
|
|
|
data.current_phase = data.phases.find(function (p) { return p.current; });
|
2022-11-19 18:52:01 +01:00
|
|
|
let template = `
|
|
|
|
<div class="card">
|
|
|
|
<header class="card-header">
|
|
|
|
<p class="card-header-title">
|
2023-02-21 16:34:40 +01:00
|
|
|
Active Recipe ({{recipe-time}}): {{name}}
|
2022-11-19 18:52:01 +01:00
|
|
|
</p>
|
|
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
|
|
{{description}}
|
2023-02-21 16:34:40 +01:00
|
|
|
<br/>
|
|
|
|
{{recipe-consumption}}
|
|
|
|
<br/>
|
|
|
|
|
2022-11-19 18:52:01 +01:00
|
|
|
<div class="tabs is-centered">
|
|
|
|
<ul>
|
|
|
|
{{#phases}}
|
2023-02-21 16:34:40 +01:00
|
|
|
<li class="{{#current}}is-active{{/current}}"><a>{{name}}{{#current}} ({{phase-time}}){{/current}}</a></li>
|
2022-11-19 18:52:01 +01:00
|
|
|
{{/phases}}
|
|
|
|
</ul>
|
|
|
|
</div>
|
2022-11-19 21:42:54 +01:00
|
|
|
{{current_phase.text}}
|
|
|
|
<br/>
|
2023-02-21 16:34:40 +01:00
|
|
|
{{phase-consumption}}
|
|
|
|
<br/>
|
2022-11-19 21:42:54 +01:00
|
|
|
<br/>
|
2023-01-06 10:54:06 +01:00
|
|
|
Next Cond: {{current_phase.nextcond}}
|
2022-11-19 21:42:54 +01:00
|
|
|
<br/>
|
2023-01-06 10:54:06 +01:00
|
|
|
On Load: {{current_phase.onload}}
|
2022-11-19 21:42:54 +01:00
|
|
|
<br/>
|
2023-01-06 10:54:06 +01:00
|
|
|
On Exit: {{current_phase.onexit}}
|
2022-11-19 18:52:01 +01:00
|
|
|
</div>
|
|
|
|
<footer class="card-footer">
|
2022-11-19 21:42:54 +01:00
|
|
|
<!-- <a class="card-footer-item is-primary">Pause</a> -->
|
|
|
|
<a onclick="next_phase()"
|
|
|
|
class="card-footer-item">Next Phase</a>
|
2023-02-21 14:02:42 +01:00
|
|
|
<a onclick="maybe_stop_recipe()"
|
2022-11-19 21:42:54 +01:00
|
|
|
class="card-footer-item">Stop</a>
|
2022-11-19 18:52:01 +01:00
|
|
|
</footer>
|
|
|
|
</div>`;
|
2023-02-21 16:34:40 +01:00
|
|
|
data['recipe-time'] = stodate(data['recipe-time'])
|
|
|
|
data['phase-time'] = stodate(data['phase-time'])
|
2022-11-19 18:52:01 +01:00
|
|
|
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);
|
2023-02-21 14:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function question_modal(data) {
|
|
|
|
let modal = document.getElementById("question-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="{{action}}">
|
|
|
|
{{tag}}
|
|
|
|
</button>
|
|
|
|
{{/options}}
|
|
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
</div>`;
|
|
|
|
modal.innerHTML = Mustache.render(template, data);
|
2022-11-19 18:52:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|