adjust layout and show ELOG page with relevant procedure
This commit is contained in:
+65
-64
@@ -1,79 +1,80 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f7f7f7;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
flex: 2;
|
||||
align-items: stretch; /* right column stretches to match left column's natural height */
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
#pump-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
flex: 1;
|
||||
/* LEFT COLUMN: a flex column whose width shrinks to fit its widest
|
||||
child. Since align-items defaults to "stretch", the other two
|
||||
divs will stretch to match whatever width the svg establishes. */
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 10px;
|
||||
background-color: #B8EBE5;
|
||||
}
|
||||
width: fit-content; /* shrink-to-fit content, i.e. the svg's width */
|
||||
}
|
||||
|
||||
.control-panel p {
|
||||
font-size: 20px;
|
||||
font-family:verdana;
|
||||
}
|
||||
.left > div {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.control-panel h {
|
||||
font-size: 32px;
|
||||
font-family:verdana;
|
||||
}
|
||||
/* No padding here: the textarea itself becomes the full-width/height
|
||||
box, so its dimensions can match the svg's rendered size exactly. */
|
||||
.bottom {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: rows;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-family:verdana;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
padding: 10px;
|
||||
background-color: #AFB0B1;
|
||||
color: black;
|
||||
.bottom textarea {
|
||||
display: block;
|
||||
width: 100%; /* matches .bottom's width, which the flex-stretch
|
||||
already ties to the svg's width */
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
border-radius: inherit;
|
||||
padding: 0.75rem 1rem;
|
||||
font: inherit;
|
||||
resize: none; /* prevent the user from breaking the matched size */
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background-color: #DADBDC;
|
||||
}
|
||||
.svg-wrap {
|
||||
padding: 0; /* let the svg define the box exactly */
|
||||
line-height: 0; /* avoid extra inline-svg whitespace gap */
|
||||
}
|
||||
|
||||
#outputtext {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
resize: none;
|
||||
}
|
||||
.svg-wrap svg {
|
||||
display: block;
|
||||
/* intrinsic size set via width/height attributes below */
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
/* RIGHT COLUMN: takes remaining horizontal space AND stretches to
|
||||
match the left column's full height (via .page's align-items: stretch) */
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex; /* so the iframe can fill 100% of it */
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
padding: 0;
|
||||
overflow: hidden; /* keep the iframe's corners inside the rounded border */
|
||||
}
|
||||
|
||||
.right iframe {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
+14
-8
@@ -9,16 +9,22 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-container">
|
||||
{% include svg %}
|
||||
<div class="control-panel">
|
||||
<div>
|
||||
|
||||
<div class="page">
|
||||
<div class="left">
|
||||
<div class="top">
|
||||
<input type=radio id="readonly", onClick="setWritable(0);" checked>view only
|
||||
<input type=radio id="writable", onClick="setWritable(1);"">ACTIVE
|
||||
</div>
|
||||
<p> Status messages </p>
|
||||
<textarea name="outputtext" id="outputtext" rows="10" cols="30" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="svg-wrap">
|
||||
{% include svg %}
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<textarea name="outputtext" id="outputtext" rows="5" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<iframe src='http://linse-c.psi.ch:8080/sample_environment/?Device={{ name }}&Title=procedure&mode=full'></iframe>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
body, html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: lightgrey;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
flex: 2;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#pump-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 10px;
|
||||
background-color: #B8EBE5;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.control-panel p {
|
||||
font-size: 20px;
|
||||
font-family:verdana;
|
||||
}
|
||||
|
||||
|
||||
.control-panel h {
|
||||
font-size: 32px;
|
||||
font-family:verdana;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: rows;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-family:verdana;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
padding: 10px;
|
||||
background-color: #AFB0B1;
|
||||
color: black;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background-color: #DADBDC;
|
||||
}
|
||||
|
||||
#outputtext {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
resize: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pump Control</title>
|
||||
<style>
|
||||
{% include css %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-container">
|
||||
{% include svg_ui %}
|
||||
<div class="control-panel">
|
||||
<h1> DIL5 Control </h1>
|
||||
<p> States </p>
|
||||
<div class="button-container">
|
||||
<button id = "test-btn" class="control-button" onclick="handleStateClick('TEST')">Test</button>
|
||||
<button id = "condense-btn" class="control-button" onclick="handleStateClick('CONDENSE')">Condense</button>
|
||||
<button id = "circulate-btn" class="control-button" onclick="handleStateClick('CIRCULATE')">Circulate</button>
|
||||
<button id = "remove-btn" class="control-button" onclick="handleStateClick('REMOVE')">Remove</button>
|
||||
<button id = "manual-btn" class="control-button" onclick="handleStateClick('MANUAL')">Manual</button>
|
||||
</div>
|
||||
<p> Status messages </p>
|
||||
<textarea name="outputtext" id="outputtext" rows="10" cols="30" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
const svgElements = {};
|
||||
|
||||
var colors = {true: "#66ffba", false: "#ff3067", error: "#ffff00", clicked: "#ffffff", ok: "#ffffff"};
|
||||
|
||||
let active_state = "MANUAL";
|
||||
|
||||
var description;
|
||||
var changeFuncs = {};
|
||||
var updateValues = {};
|
||||
|
||||
function handle_describing(action, modpar, data) {
|
||||
description = data;
|
||||
for (const mod of Object.keys(data.modules)) {
|
||||
const mdesc = data.modules[mod];
|
||||
if (! mdesc) {
|
||||
console.log('bad mod', mod)
|
||||
continue;
|
||||
}
|
||||
console.log(mdesc);
|
||||
const elems = document.querySelectorAll("[id^='" + mod + ":']");
|
||||
let stateElement = null;
|
||||
let clickElement = null;
|
||||
elems.forEach(elem => {
|
||||
let [parmod, ...func] = elem.id.split('-');
|
||||
const [mod, param] = parmod.split(':');
|
||||
// console.log(mod, param, func);
|
||||
if (func.includes("click")) {
|
||||
clickElement = elem;
|
||||
addClickListener(elem, mod, toggleTarget);
|
||||
}
|
||||
if (func.includes("fault")) {
|
||||
changeFuncs[mod + ":status"] = function (value) {
|
||||
changeFill(elem, value[0] < 400 ? "ok" : "error");
|
||||
}
|
||||
}
|
||||
if (func.includes("state")) {
|
||||
stateElement = elem;
|
||||
changeFuncs[mod + ":" + (param || 'value')] = function (value) {
|
||||
changeFill(elem, value);
|
||||
}
|
||||
}
|
||||
if (func.includes("text")) {
|
||||
const pat = elem.textContent;
|
||||
changeFuncs[mod + ":" + (param || 'value')] = function (value) {
|
||||
elem.textContent = pat.replace('*', value);
|
||||
elem.style.fill = "#d5d5d5";
|
||||
}
|
||||
}
|
||||
if (func.includes("command")) {
|
||||
console.log(func, mod, param);
|
||||
addClickListener(elem, mod + ":" + param, sendCommand);
|
||||
}
|
||||
})
|
||||
if (stateElement && clickElement) {
|
||||
clickElement.stateElement = stateElement;
|
||||
}
|
||||
}
|
||||
console.log('ACTIVATE', changeFuncs);
|
||||
doSend("activate");
|
||||
}
|
||||
|
||||
function handle_update(action, modpar, data) {
|
||||
if (modpar.startsWith('compressor')) console.log(action, modpar, data);
|
||||
if (modpar in changeFuncs) {
|
||||
updateValues[modpar] = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
handle_reply = handle_update;
|
||||
handle_changed = handle_update;
|
||||
|
||||
function handle_error_update(action, modpar, data) {
|
||||
}
|
||||
|
||||
function handleMSG(msg) {
|
||||
const [action, modpar, ...tail] = msg.split(" ");
|
||||
let data = tail.join(' ');
|
||||
if (data) {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
handler = window["handle_" + action];
|
||||
if (handler) {
|
||||
handler(action, modpar, data);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function processUpdates() {
|
||||
let updates = updateValues;
|
||||
updateValues = {};
|
||||
for (const [modpar, value] of Object.entries(updates)) {
|
||||
if (modpar.startsWith('compressor')) console.log('process', modpar, value);
|
||||
changeFuncs[modpar](value);
|
||||
}
|
||||
requestAnimationFrame(processUpdates);
|
||||
}
|
||||
|
||||
|
||||
function handleStateClick(state) {
|
||||
changeState("dil", state);
|
||||
document.getElementById(state.toLowerCase() + "-btn").style.backgroundColor = "white";
|
||||
document.getElementById(active_state.toLowerCase() + "-btn").style.backgroundColor = "rgb(175, 176, 177)";
|
||||
active_state = state;
|
||||
}
|
||||
|
||||
|
||||
function updateState(state) {
|
||||
document.getElementById(state.toLowerCase() + "-btn").style.backgroundColor = "white";
|
||||
document.getElementById(active_state.toLowerCase() + "-btn").style.backgroundColor = "rgb(175, 176, 177)";
|
||||
active_state = state;
|
||||
}
|
||||
|
||||
function changeFill(element, state) {
|
||||
stateElement = element.stateElement || element;
|
||||
stateElement.state = state;
|
||||
if (state in colors) {
|
||||
stateElement.style.fill = colors[state];
|
||||
} else {
|
||||
stateElement.style.fill = "#eaeaea" ;
|
||||
}
|
||||
}
|
||||
|
||||
function writeToScreen(message) {
|
||||
const output = document.getElementById("outputtext");
|
||||
output.value += message;
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
function doConnect(uri) {
|
||||
websocket = new WebSocket(uri);
|
||||
websocket.onopen = evt => onOpen(evt);
|
||||
websocket.onclose = evt => onClose(evt);
|
||||
websocket.onmessage = evt => handleMSG(evt.data);
|
||||
websocket.onerror = evt => onError(evt);
|
||||
}
|
||||
|
||||
function onOpen(evt) {
|
||||
writeToScreen("Connected to DIL5\n");
|
||||
doSend("describe");
|
||||
/*
|
||||
for (var mod in ctr_state) {
|
||||
readValue(mod);
|
||||
}
|
||||
startPolling();
|
||||
*/
|
||||
}
|
||||
|
||||
function changeState(mod, value) {
|
||||
doSend(`change ${mod}:target "${value}"`);
|
||||
//writeToScreen(`change ${mod}:target "${value}"\n`);
|
||||
}
|
||||
|
||||
function onClose(evt) {
|
||||
writeToScreen("disconnected\n");
|
||||
}
|
||||
|
||||
function onError(evt) {
|
||||
if (evt.data === undefined) {
|
||||
writeToScreen("Unable to connect to DIL5 as websocket is not connectable\n");
|
||||
} else {
|
||||
writeToScreen("error: " + evt.data + '\n');
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function doSend(message) {
|
||||
console.log('>', message);
|
||||
websocket.send(message);
|
||||
}
|
||||
|
||||
function toggleTarget(element, mod) {
|
||||
stateElement = element.stateElement || element
|
||||
const newState = stateElement.state === false;
|
||||
console.log('click', element, mod, stateElement.state, newState);
|
||||
changeFill(element, "clicked");
|
||||
doSend(`change ${mod}:target ${newState}`);
|
||||
}
|
||||
|
||||
function sendCommand(element, modcmd) {
|
||||
console.log('command', modcmd);
|
||||
doSend(`do ${modcmd}`);
|
||||
}
|
||||
|
||||
function addClickListener(element, arg, func) {
|
||||
element.addEventListener('click', () => { func(element, arg); });
|
||||
element.classList.add('clickable');
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
doConnect('ws://' + window.location.hostname + ':8010/');
|
||||
requestAnimationFrame(processUpdates);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+8
-8
@@ -1227,7 +1227,7 @@
|
||||
d="m 26.050418,106.41723 -0.0049,2.16024 -0.0049,2.16023 -1.699686,-1.08467 -1.699686,-1.08468 1.704571,-1.07556 z m -6.818286,4.30549 0.003,-2.16022 0.003,-2.16023 1.701493,1.08304 1.701493,1.08304 -1.704571,1.07718 z" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="24.996986"
|
||||
y="14.393418"
|
||||
id="p3:-text"><tspan
|
||||
@@ -1237,7 +1237,7 @@
|
||||
style="font-size:2.82223px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">* mbar</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="26.113924"
|
||||
y="46.21619"
|
||||
id="p4:-text"><tspan
|
||||
@@ -1247,7 +1247,7 @@
|
||||
style="font-size:2.82223px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">* mbar</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="25.756571"
|
||||
y="132.41373"
|
||||
id="p5:-text"><tspan
|
||||
@@ -1257,7 +1257,7 @@
|
||||
style="font-size:2.82222px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">* mbar</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="85.813934"
|
||||
y="114.90757"
|
||||
id="p2:-text"><tspan
|
||||
@@ -1267,7 +1267,7 @@
|
||||
style="font-size:2.82222px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">* mbar</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="54.460369"
|
||||
y="121.08282"
|
||||
id="p1:-text"><tspan
|
||||
@@ -1277,7 +1277,7 @@
|
||||
style="font-size:2.82223px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">* mbar</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="19.930603"
|
||||
y="22.033382"
|
||||
id="turbopump:_speed-text"><tspan
|
||||
@@ -1287,7 +1287,7 @@
|
||||
style="font-size:2.82223px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">speed: * Hz</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:2.82223px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="19.836363"
|
||||
y="25.367538"
|
||||
id="turbopump:_current-text"><tspan
|
||||
@@ -1297,7 +1297,7 @@
|
||||
style="font-size:2.82223px;fill:#d5d5d5;fill-opacity:1;stroke-width:0.264583">current: * %</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#d5d5d5;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||
x="6.1898136"
|
||||
y="101.19585"
|
||||
id="text22594"><tspan
|
||||
|
||||
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
+2
-1
@@ -45,6 +45,7 @@ function handle_describing(action, modpar, data) {
|
||||
}
|
||||
if (func.includes("text")) {
|
||||
const pat = elem.textContent;
|
||||
console.log('X', elem)
|
||||
changeFuncs[mod + ":" + (param || 'value')] = function (value) {
|
||||
let [repl, dot, fix] = pat.match(/\*(\.)?(\d)?/);
|
||||
if (dot) {
|
||||
@@ -53,7 +54,7 @@ function handle_describing(action, modpar, data) {
|
||||
formatted = formatValue(value, fix || 5);
|
||||
}
|
||||
elem.textContent = pat.replace(repl, formatted);
|
||||
// elem.style.color = "000000";
|
||||
console.log('Y', elem)
|
||||
}
|
||||
}
|
||||
if (func.includes("command")) {
|
||||
|
||||
Reference in New Issue
Block a user