Curves settings UI
This commit is contained in:
@ -13,6 +13,9 @@
|
||||
|
||||
<!-- Components -->
|
||||
|
||||
<script src="components/curves_settings_popup/color_selector/color_selector.js"></script>
|
||||
<script src="components/curves_settings_popup/curves_settings_popup.js"></script>
|
||||
|
||||
<script src="components/export_popup/export_popup.js"></script>
|
||||
<script src="components/action_entry/action_entry.js"></script>
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
.color-selector-select{
|
||||
width:57px;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
class ColorSelector extends HTMLElement{
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.render();
|
||||
}
|
||||
|
||||
setValue(value){
|
||||
let options = this.querySelectorAll("select option");
|
||||
|
||||
for(let option of options){
|
||||
if(option.value == value){
|
||||
option.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValue(){
|
||||
return this.getElementsByTagName("select")[0].value;
|
||||
}
|
||||
|
||||
render(){
|
||||
this.innerHTML = `
|
||||
<link rel="stylesheet" href="components/curves_settings_popup/color_selector/color_selector.css"/>
|
||||
<select class="color-selector-select">
|
||||
<option value=""></option>
|
||||
<option value="-1">Auto</option>
|
||||
<option value="#FF0000">Red</option>
|
||||
<option value="#00FF00">Lime</option>
|
||||
<option value="#0000FF">Blue</option>
|
||||
<option value="#FF00FF">Magenta</option>
|
||||
<option value="#FFFF00">Yellow</option>
|
||||
<option value="#00FFFF">Cyan</option>
|
||||
<option value="#000000">Black</option>
|
||||
<option value="#FFA500">Orange</option>
|
||||
<option value="#006400">Dark green</option>
|
||||
<option value="#9400D3">Dark violet</option>
|
||||
<option value="#A52A2A">Brown</option>
|
||||
<option value="#87CEEB">Sky blue</option>
|
||||
<option value="#FF69B4">Hot pink</option>
|
||||
<option value="#FFFFE0">Light yellow</option>
|
||||
</select>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("sea-color-selector", ColorSelector);
|
@ -0,0 +1,119 @@
|
||||
#curves-settings-popup{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 53;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
margin-top: 30px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
#curves-settings-popup-container{
|
||||
width: 380px;
|
||||
height: 100%;
|
||||
border: 2px solid black;
|
||||
margin: auto;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#curves-settings-popup-header{
|
||||
height: 40px;
|
||||
display: flex;
|
||||
border-bottom: 2px solid black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#curves-settings-popup-header span{
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#curves-settings-popup-header img{
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#curves-settings-popup-content{
|
||||
padding: 10px;
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
|
||||
.button-center-wrapper{
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scrollable-content{
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#curves-settings-popup-table{
|
||||
width: 335px;
|
||||
}
|
||||
#curves-settings-popup-table,
|
||||
#curves-settings-popup-table tr th,
|
||||
#curves-settings-popup-table tr td
|
||||
{
|
||||
border-collapse: collapse;
|
||||
border: 1px solid black;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#curves-settings-popup-table th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-input{
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.bin-cell{
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
td img{
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.variable-cell, .parameter-cell, .cat-cell{
|
||||
width: 73px;
|
||||
}
|
||||
.color-cell{
|
||||
width: 57px;
|
||||
}
|
||||
|
||||
.unit-cell{
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
#curves-settings-popup-footer{
|
||||
height: 40px;
|
||||
display: flex;
|
||||
border-top: 2px solid black;
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
#curves-settings-popup-footer button{
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-right: 10px;
|
||||
}
|
250
client/components/curves_settings_popup/curves_settings_popup.js
Normal file
250
client/components/curves_settings_popup/curves_settings_popup.js
Normal file
@ -0,0 +1,250 @@
|
||||
class CurvesSettingsPopup extends HTMLElement{
|
||||
|
||||
constructor(applySettingsCallback){
|
||||
super();
|
||||
this.applySettingsCallback = applySettingsCallback;
|
||||
}
|
||||
|
||||
initTable(){
|
||||
|
||||
let userConfiguration = this.getUserConfiguration();
|
||||
let tbody = this.querySelector("#curves-settings-popup-table tbody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
for(let lineConfiguration of userConfiguration){
|
||||
this.createRow(tbody, lineConfiguration);
|
||||
}
|
||||
|
||||
this.createRow(tbody); // we add another row by default at the end
|
||||
}
|
||||
|
||||
doApplySettingsCallback(){
|
||||
|
||||
try{
|
||||
let localStorageBuffer = [];
|
||||
let formattedUserConfiguration = this.getFormattedUserConfiguration(localStorageBuffer);
|
||||
|
||||
localStorage.clear();
|
||||
this.saveUserConfiguration(localStorageBuffer);
|
||||
|
||||
this.hide();
|
||||
this.applySettingsCallback(formattedUserConfiguration);
|
||||
}catch{}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds localStorageBuffer with user rows to save, while formatting each row for later API call
|
||||
* Throws an error if a row is invalid (no variable is given for a non empty line)
|
||||
* @param {[]} localStorageBuffer an array feeded by this method, that contains the JS object representing the data out of a line
|
||||
* @returns the formatted user configuration object to be passed as the payload to the server
|
||||
*/
|
||||
getFormattedUserConfiguration(localStorageBuffer){
|
||||
let formatedUserConfiguration = {};
|
||||
|
||||
let rows = this.querySelectorAll("tbody tr");
|
||||
for(let row of rows){
|
||||
|
||||
let configurationLineObject = this.getRowValues(row);
|
||||
let formmatedLineConfiguration = {...configurationLineObject};
|
||||
|
||||
if(!formmatedLineConfiguration.hasOwnProperty("variable") && Object.keys(formmatedLineConfiguration).length > 0){
|
||||
alertify.error("Variable not defined for some row(s).");
|
||||
throw Error;
|
||||
}else{
|
||||
let key = formmatedLineConfiguration["variable"];
|
||||
delete formmatedLineConfiguration["variable"];
|
||||
if(formmatedLineConfiguration.hasOwnProperty("parameter")){
|
||||
key += "." + formmatedLineConfiguration["parameter"];
|
||||
delete formmatedLineConfiguration["parameter"];
|
||||
}
|
||||
if(Object.keys(formmatedLineConfiguration).length > 0){
|
||||
formatedUserConfiguration[key] = formmatedLineConfiguration;
|
||||
localStorageBuffer.push(configurationLineObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
return formatedUserConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data out of the given HTML row as a JSON object
|
||||
* @param {*} row An HTML tr element of the table
|
||||
* @returns A JS object containing the data out of the HTML element : there is a key if the value is not an empty string
|
||||
*/
|
||||
getRowValues(row){
|
||||
let configuration = {};
|
||||
|
||||
for(let cell of row.children){
|
||||
let content = cell.children[0]; //there is only one child per cell
|
||||
if(content.nodeName == "INPUT" && content.value !== ""){
|
||||
configuration[content.name] = content.value;
|
||||
}
|
||||
else if (content.nodeName == "SEA-COLOR-SELECTOR" && content.getValue() !== ""){
|
||||
configuration["color"] = content.getValue();
|
||||
}
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
getUserConfiguration(){
|
||||
let userConfiguration = [];
|
||||
for(let i = 0; i < localStorage.length; i++){
|
||||
userConfiguration.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
|
||||
}
|
||||
return userConfiguration;
|
||||
}
|
||||
|
||||
saveUserConfiguration(userConfiguration){
|
||||
for(let i = 0; i < userConfiguration.length; i++){
|
||||
localStorage.setItem(i, JSON.stringify(userConfiguration[i]));
|
||||
}
|
||||
}
|
||||
|
||||
addNewRowIfEmpty(){
|
||||
let tbody = this.querySelector("#curves-settings-popup-table tbody");
|
||||
if(tbody.childNodes.length == 0){
|
||||
this.createRow(tbody);
|
||||
}
|
||||
}
|
||||
|
||||
addRow(){
|
||||
let tbody = this.querySelector("#curves-settings-popup-table tbody");
|
||||
this.createRow(tbody);
|
||||
}
|
||||
|
||||
show(){
|
||||
this.style.visibility = "visible";
|
||||
this.initTable();
|
||||
window.addEventListener("click", this.backgroundClickCallback);
|
||||
}
|
||||
|
||||
hide(){
|
||||
this.style.visibility = "hidden";
|
||||
window.removeEventListener("click", this.backgroundClickCallback);
|
||||
}
|
||||
|
||||
backgroundClickCallback = ({target}) => {
|
||||
if(target.id == "curves-settings-popup"){
|
||||
this.doApplySettingsCallback();
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(){
|
||||
this.render();
|
||||
this.hide();
|
||||
this.getElementsByTagName("img")[0].onclick = () => {this.doApplySettingsCallback();};
|
||||
this.getElementsByClassName("add-row-button")[0].onclick = () => {this.addRow();};
|
||||
this.getElementsByClassName("cancel-button")[0].onclick = () => {this.hide();};
|
||||
this.getElementsByClassName("apply-button")[0].onclick = () => {this.doApplySettingsCallback();};
|
||||
}
|
||||
|
||||
//
|
||||
/**
|
||||
* Adds a row to tbody
|
||||
* The process is disigned in this way because seaColorSelector.setValue is called, and its content has to be
|
||||
* generated before calling this method, that is why we need to append the newly created HTML element
|
||||
* as soon as possible.
|
||||
* If lineConfiguration == null, an empty row is added to the table
|
||||
* @param {*} tbody - The HTML element of the table body
|
||||
* @param {*} lineConfiguration - The object representing one line of configuration on the client side
|
||||
* @returns
|
||||
*/
|
||||
createRow(tbody, lineConfiguration = null){
|
||||
|
||||
let row = document.createElement("tr");
|
||||
tbody.appendChild(row)
|
||||
let binCell = document.createElement("td");
|
||||
|
||||
let binImg = document.createElement("img");
|
||||
binImg.src = "res/bin.png";
|
||||
binImg.classList.add("bin-cell");
|
||||
binImg.onclick = () => {
|
||||
binImg.parentNode.parentNode.remove();
|
||||
this.addNewRowIfEmpty();
|
||||
}
|
||||
binCell.appendChild(binImg);
|
||||
row.append(binCell)
|
||||
|
||||
this.createTextInput(row, lineConfiguration, "variable")
|
||||
|
||||
this.createTextInput(row, lineConfiguration, "parameter")
|
||||
|
||||
this.createTextInput(row, lineConfiguration, "cat")
|
||||
|
||||
let colorCell = document.createElement("td");
|
||||
let seaColorSelector = new ColorSelector();
|
||||
row.append(colorCell)
|
||||
colorCell.appendChild(seaColorSelector); //need to first append it before calling setValue
|
||||
seaColorSelector.setValue("");
|
||||
if(lineConfiguration != null && lineConfiguration.hasOwnProperty('color')){
|
||||
seaColorSelector.setValue(lineConfiguration['color']);
|
||||
}
|
||||
|
||||
this.createTextInput(row, lineConfiguration, "unit")
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
createTextInput(row, lineConfiguration, type){
|
||||
let cell = document.createElement("td");
|
||||
let input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.spellcheck = false
|
||||
input.autocorrect = "off"
|
||||
input.name = type;
|
||||
input.classList.add(`${type}-cell`, "text-input");
|
||||
input.value = "";
|
||||
if(lineConfiguration != null && lineConfiguration.hasOwnProperty(type)){
|
||||
input.value = lineConfiguration[type];
|
||||
}
|
||||
cell.appendChild(input);
|
||||
row.append(cell)
|
||||
}
|
||||
|
||||
render(){
|
||||
this.innerHTML = `
|
||||
<link rel="stylesheet" href="components/curves_settings_popup/curves_settings_popup.css"/>
|
||||
<div id="curves-settings-popup">
|
||||
<div id="curves-settings-popup-container">
|
||||
|
||||
<div id="curves-settings-popup-header">
|
||||
<span>Curves settings</span>
|
||||
<img src="res/close.png"/>
|
||||
</div>
|
||||
|
||||
<div id="curves-settings-popup-content">
|
||||
|
||||
<div class="scrollable-content">
|
||||
<table id="curves-settings-popup-table">
|
||||
<thead style="position: sticky;">
|
||||
<tr>
|
||||
<th class="bin-cell"></th>
|
||||
<th class="variable-cell">Variable</th>
|
||||
<th class="parameter-cell">Parameter</th>
|
||||
<th class="cat-cell">Category</th>
|
||||
<th class="color-cell">Color</th>
|
||||
<th class="unit-cell">Unit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="button-center-wrapper">
|
||||
<button class="add-row-button">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="curves-settings-popup-footer">
|
||||
<button class="cancel-button">Cancel</button>
|
||||
<button class="apply-button">Apply</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("sea-curves-settings-popup", CurvesSettingsPopup);
|
@ -249,6 +249,7 @@ let globalIndicators = (function (){
|
||||
let datesIndicator = new DateIndicator(leftDate);
|
||||
datesIndicator.addEventListener("click", function () {
|
||||
exportPopup.hide();
|
||||
curvesSettingsPopup.hide();
|
||||
menuGraphicsPopup.hide();
|
||||
datesPopup.show();
|
||||
})
|
||||
@ -277,14 +278,17 @@ function loadGraphicsMenu(panel){
|
||||
|
||||
menuGraphicsPopup = new MenuPopup();
|
||||
let exportActionEntry = new ActionEntry("Export", graphs.displayExportPopup, () => {menuGraphicsPopup.hide()});
|
||||
let curvesSettingsActionEntry = new ActionEntry("Curves settings", () => {curvesSettingsPopup.show();}, () => {menuGraphicsPopup.hide()});
|
||||
let removeCursorHelpEntry = new HelpEntry("How to remove the cursor", "You can double click/tap on any graph.");
|
||||
menuGraphicsPopup.addEntry(exportActionEntry)
|
||||
menuGraphicsPopup.addEntry(curvesSettingsActionEntry);
|
||||
menuGraphicsPopup.addHorizontalDivider();
|
||||
menuGraphicsPopup.addEntry(removeCursorHelpEntry);
|
||||
|
||||
let graphicsMenuControl = new Control("res/menu_white.png", "res/menu_white.png", "Menu", () => {
|
||||
datesPopup.hide();
|
||||
exportPopup.hide();
|
||||
curvesSettingsPopup.hide();
|
||||
menuGraphicsPopup.show();
|
||||
});
|
||||
panel.appendChild(menuGraphicsPopup);
|
||||
@ -315,6 +319,18 @@ function exportCallback(selectedVariables, startDateTimeMs, endDateTimeMs, nan,
|
||||
a.click()
|
||||
}
|
||||
|
||||
let curvesSettingsPopup = undefined;
|
||||
|
||||
function loadCurvesSettingsPopup(){
|
||||
let graphsContainer = document.getElementsByClassName("graphs-container")[0];
|
||||
curvesSettingsPopup = new CurvesSettingsPopup(applySettingsCallback);
|
||||
graphsContainer.appendChild(curvesSettingsPopup);
|
||||
}
|
||||
|
||||
function applySettingsCallback(userConfiguration){
|
||||
console.log(JSON.stringify(userConfiguration));
|
||||
}
|
||||
|
||||
let graphs = (function (){
|
||||
let dataset_to_graph_map = {}; // a dictionnary mapping a variable name to a two values array, containing its graph index and its position inside the graph
|
||||
let blocks, liveMode=true, top_vars=[], bottom_vars=[];
|
||||
@ -937,6 +953,7 @@ let graphs = (function (){
|
||||
});
|
||||
|
||||
loadExportPopup();
|
||||
loadCurvesSettingsPopup();
|
||||
loadDatesPopup();
|
||||
globalIndicators.loadIndicators(graphicsPanel);
|
||||
globalControls.loadControls(graphicsPanel);
|
||||
|
BIN
client/res/bin.png
Normal file
BIN
client/res/bin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -179,6 +179,8 @@ def subdir_test_file(file):
|
||||
resp = flask.send_file("client/test/"+file, mimetype=guess_mimetype(file))
|
||||
return resp
|
||||
|
||||
@app.route('/components/curves_settings_popup/color_selector/<file>')
|
||||
@app.route('/components/curves_settings_popup/<file>')
|
||||
@app.route('/components/action_entry/<file>')
|
||||
@app.route('/components/export_popup/<file>')
|
||||
@app.route('/components/dates_popup/<file>')
|
||||
|
Reference in New Issue
Block a user