forked from NPM/numass-framework
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ae7e7c3d4 | |||
b0154ac187 | |||
9f60c14bd9 | |||
2177dd5b42 | |||
e367bd62f1 | |||
650e789fc5 |
@ -112,6 +112,7 @@ fun EventTarget.deviceStateIndicator(connection: DeviceDisplayFX<*>, state: Stri
|
|||||||
fun Node.deviceStateToggle(connection: DeviceDisplayFX<*>, state: String, title: String = state) {
|
fun Node.deviceStateToggle(connection: DeviceDisplayFX<*>, state: String, title: String = state) {
|
||||||
if (connection.device.stateNames.contains(state)) {
|
if (connection.device.stateNames.contains(state)) {
|
||||||
togglebutton(title) {
|
togglebutton(title) {
|
||||||
|
this.id = title
|
||||||
selectedProperty().addListener { _, oldValue, newValue ->
|
selectedProperty().addListener { _, oldValue, newValue ->
|
||||||
if (oldValue != newValue) {
|
if (oldValue != newValue) {
|
||||||
connection.device.states[state] = newValue
|
connection.device.states[state] = newValue
|
||||||
|
@ -9,7 +9,7 @@ import hep.dataforge.optional
|
|||||||
import javafx.scene.Scene
|
import javafx.scene.Scene
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import tornadofx.*
|
import tornadofx.App
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,8 +46,10 @@ abstract class NumassControlApplication<in D : Device> : App() {
|
|||||||
|
|
||||||
abstract fun getDeviceMeta(config: Meta): Meta
|
abstract fun getDeviceMeta(config: Meta): Meta
|
||||||
|
|
||||||
|
fun getDeviceConfig() : Meta = getConfig(this).optional.orElseGet { readResourceMeta("config/devices.xml") }
|
||||||
|
|
||||||
private fun setupDevice(): D {
|
private fun setupDevice(): D {
|
||||||
val config = getConfig(this).optional.orElseGet { readResourceMeta("config/devices.xml") }
|
val config = getDeviceConfig()
|
||||||
|
|
||||||
val ctx = setupContext(config)
|
val ctx = setupContext(config)
|
||||||
val deviceConfig = getDeviceMeta(config)
|
val deviceConfig = getDeviceMeta(config)
|
||||||
|
@ -100,7 +100,6 @@ fun getConfig(app: Application): Meta? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun findDeviceMeta(config: Meta, criterion: (Meta) -> Boolean): Meta? {
|
fun findDeviceMeta(config: Meta, criterion: (Meta) -> Boolean): Meta? {
|
||||||
return config.getMetaList("device").stream().filter(criterion).findFirst().nullable
|
return config.getMetaList("device").stream().filter(criterion).findFirst().nullable
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import javafx.stage.Stage
|
|||||||
/**
|
/**
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
class ReadVac : NumassControlApplication<VacCollectorDevice>() {
|
open class ReadVac : NumassControlApplication<VacCollectorDevice>() {
|
||||||
|
|
||||||
override val deviceFactory = VacDeviceFactory()
|
override val deviceFactory = VacDeviceFactory()
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ class VacCollectorDisplay : DeviceDisplayFX<VacCollectorDevice>() {
|
|||||||
}
|
}
|
||||||
separator(Orientation.VERTICAL)
|
separator(Orientation.VERTICAL)
|
||||||
togglebutton("Log") {
|
togglebutton("Log") {
|
||||||
|
this.id = "Log"
|
||||||
isSelected = false
|
isSelected = false
|
||||||
LogFragment().apply {
|
LogFragment().apply {
|
||||||
addLogHandler(device.logger)
|
addLogHandler(device.logger)
|
||||||
|
@ -121,6 +121,7 @@ open class VacDisplay : DeviceDisplayFX<Sensor>() {
|
|||||||
value = "---"
|
value = "---"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
id = device.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +162,7 @@ open class VacDisplay : DeviceDisplayFX<Sensor>() {
|
|||||||
minHeight = 30.0
|
minHeight = 30.0
|
||||||
vgrow = Priority.ALWAYS
|
vgrow = Priority.ALWAYS
|
||||||
switch("Power") {
|
switch("Power") {
|
||||||
|
this.id = "power"
|
||||||
alignment = Pos.CENTER
|
alignment = Pos.CENTER
|
||||||
booleanStateProperty("power").bindBidirectional(selectedProperty())
|
booleanStateProperty("power").bindBidirectional(selectedProperty())
|
||||||
}
|
}
|
||||||
|
42
numass-web-control/.gitignore
vendored
Normal file
42
numass-web-control/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
34
numass-web-control/build.gradle.kts
Normal file
34
numass-web-control/build.gradle.kts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
plugins {
|
||||||
|
application
|
||||||
|
id("org.openjfx.javafxplugin") version "0.1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "inr.numass"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
javafx {
|
||||||
|
modules("javafx.controls", "javafx.web")
|
||||||
|
version = "16"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.9.1"))
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
implementation("io.ktor:ktor-server-core:2.3.7")
|
||||||
|
implementation("io.ktor:ktor-server-netty:2.3.7")
|
||||||
|
implementation("io.ktor:ktor-server-websockets:2.3.7")
|
||||||
|
api(project(":numass-control:vac"))
|
||||||
|
api(project(":dataforge-core:dataforge-json"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("inr.numass.webcontrol.ServerKt")
|
||||||
|
}
|
152
numass-web-control/src/main/java/inr/numass/webcontrol/Server.kt
Normal file
152
numass-web-control/src/main/java/inr/numass/webcontrol/Server.kt
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package inr.numass.webcontrol
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||||
|
import ch.qos.logback.core.AppenderBase
|
||||||
|
import hep.dataforge.get
|
||||||
|
import hep.dataforge.io.JSONMetaWriter
|
||||||
|
import inr.numass.control.readvac.ReadVac
|
||||||
|
import inr.numass.control.readvac.VacCollectorDevice
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.engine.*
|
||||||
|
import io.ktor.server.http.content.*
|
||||||
|
import io.ktor.server.netty.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import javafx.application.Application
|
||||||
|
import javafx.scene.control.ToggleButton
|
||||||
|
import javafx.stage.Stage
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.controlsfx.control.ToggleSwitch
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class Connection(val session: DefaultWebSocketServerSession) {
|
||||||
|
companion object {
|
||||||
|
val lastId = AtomicInteger(0)
|
||||||
|
}
|
||||||
|
val name = "user${lastId.getAndIncrement()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReadVacSvr : ReadVac() {
|
||||||
|
var server : NettyApplicationEngine? = null
|
||||||
|
private var device : VacCollectorDevice? = null
|
||||||
|
override fun setupStage(stage: Stage, device: VacCollectorDevice) {
|
||||||
|
super.setupStage(stage, device)
|
||||||
|
this.device = device
|
||||||
|
}
|
||||||
|
override fun start(stage: Stage) {
|
||||||
|
super.start(stage)
|
||||||
|
val measureButton = stage.scene.lookup("#Measure") as ToggleButton
|
||||||
|
val storeButton = stage.scene.lookup("#Store") as ToggleButton
|
||||||
|
val sensors = this@ReadVacSvr.getDeviceMeta(this@ReadVacSvr.getDeviceConfig()).getMetaList("sensor").map { it["name"] }
|
||||||
|
this.server = embeddedServer(Netty, port = 8000) {
|
||||||
|
install(WebSockets)
|
||||||
|
routing {
|
||||||
|
staticFiles("/", Paths.get(this.javaClass.classLoader.getResource("index.html").toURI()).toFile().parentFile)
|
||||||
|
val connections = Collections.synchronizedSet<Connection?>(LinkedHashSet())
|
||||||
|
for (sensor in sensors) {
|
||||||
|
(stage.scene.lookup("#${sensor}") as ToggleSwitch).selectedProperty().addListener{ _, oldValue, newValue ->
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
connections.forEach {
|
||||||
|
runBlocking {
|
||||||
|
it.session.send("{ \"sensor\": {\"${sensor}\": ${newValue}}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measureButton.selectedProperty().addListener { _, oldValue, newValue ->
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
connections.forEach {
|
||||||
|
runBlocking {
|
||||||
|
it.session.send("{ \"measurement\": {\"value\": ${newValue}}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storeButton.selectedProperty().addListener { _, oldValue, newValue ->
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
connections.forEach {
|
||||||
|
runBlocking {
|
||||||
|
it.session.send("{ \"storing\": {\"value\": ${newValue}}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(device!!.logger as ch.qos.logback.classic.Logger).addAppender(
|
||||||
|
object : AppenderBase<ILoggingEvent>() {
|
||||||
|
override fun append(eventObject: ILoggingEvent) {
|
||||||
|
synchronized(this) {
|
||||||
|
connections.forEach {
|
||||||
|
runBlocking {
|
||||||
|
it.session.send("{ \"log\": {\"value\": \"${eventObject}\"}}") // FIXME: '"' in eventObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
name = "serverLogger"
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
webSocket("/echo") {
|
||||||
|
val thisConnection = Connection(this)
|
||||||
|
connections += thisConnection
|
||||||
|
try {
|
||||||
|
// Sending device list and initial UI state
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
JSONMetaWriter.write(stream, this@ReadVacSvr.getDeviceMeta(this@ReadVacSvr.getDeviceConfig()))
|
||||||
|
send("{ \"devices\": $stream, \"storing\": {\"value\": ${storeButton.selectedProperty().get()}}, \"measurement\": {\"value\": ${measureButton.selectedProperty().get()}} }")
|
||||||
|
for (sensor in sensors) { send("{ \"sensor\": {\"${sensor}\": ${(stage.scene.lookup("#${sensor}") as ToggleSwitch).selectedProperty().get()}}}") }
|
||||||
|
|
||||||
|
for (frame in incoming) {
|
||||||
|
frame as? Frame.Text ?: continue
|
||||||
|
when (frame.readText()) {
|
||||||
|
"measurePressed" -> {
|
||||||
|
measureButton.fire()
|
||||||
|
}
|
||||||
|
"storePressed" -> {
|
||||||
|
storeButton.fire()
|
||||||
|
}
|
||||||
|
"logPressed" -> {
|
||||||
|
//logButton.fire()
|
||||||
|
}
|
||||||
|
"bye" -> close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println(e.localizedMessage)
|
||||||
|
} finally {
|
||||||
|
println("Removing $thisConnection")
|
||||||
|
connections -= thisConnection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get ("/api") {
|
||||||
|
call.respondText(call.parameters.toString())
|
||||||
|
}
|
||||||
|
get ("/api/devices") {
|
||||||
|
call.respondOutputStream(ContentType.Application.Json, HttpStatusCode.OK, null) {
|
||||||
|
JSONMetaWriter.write(this, this@ReadVacSvr.getDeviceMeta(this@ReadVacSvr.getDeviceConfig()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server!!.start(wait = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
super.stop()
|
||||||
|
this.server?.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Application.launch(ReadVacSvr::class.java)
|
||||||
|
}
|
||||||
|
|
41
numass-web-control/src/main/resources/index.html
Normal file
41
numass-web-control/src/main/resources/index.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Nu-mass vacuum measurements</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js" charset="utf-8"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="logBar"></div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<button id="measure" type="button" class="myButton toolbarButton">Measure</button>
|
||||||
|
<button id="store" type="button" class="myButton toolbarButton">Store</button>
|
||||||
|
|
||||||
|
<button id="log" type="button" class="logButton toolbarButton">Log</button>
|
||||||
|
</div>
|
||||||
|
<div id="lower">
|
||||||
|
<div id="plot">
|
||||||
|
</div>
|
||||||
|
<div id="sidebar">
|
||||||
|
<!--
|
||||||
|
<div class="card">
|
||||||
|
<div class="upperSection">
|
||||||
|
<input class="switch" type="checkbox">
|
||||||
|
</div>
|
||||||
|
<div class="middleSection">
|
||||||
|
<span>Undefined</span>
|
||||||
|
<span>mbar</span>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="powerSection">
|
||||||
|
<span>Power</span>
|
||||||
|
<input class="switch" type="checkbox">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
129
numass-web-control/src/main/resources/script.js
Normal file
129
numass-web-control/src/main/resources/script.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Creates device card
|
||||||
|
function drawCard(device_name = "Default", col = "red", measure = "---", power = false) {
|
||||||
|
let card = document.createElement("div")
|
||||||
|
card.classList = "card"
|
||||||
|
|
||||||
|
// Create upper section of a card
|
||||||
|
let upperSection = document.createElement("div")
|
||||||
|
upperSection.classList = "upperSection"
|
||||||
|
let name = document.createElement("span")
|
||||||
|
name.style.fontWeight = "bolder"
|
||||||
|
let p_toggle = document.createElement("input")
|
||||||
|
p_toggle.id = device_name
|
||||||
|
p_toggle.type= "checkbox"
|
||||||
|
p_toggle.classList = "switch"
|
||||||
|
name.textContent = device_name
|
||||||
|
upperSection.appendChild(name)
|
||||||
|
upperSection.appendChild(p_toggle)
|
||||||
|
card.appendChild(upperSection)
|
||||||
|
|
||||||
|
card.appendChild(document.createElement("hr"))
|
||||||
|
// Create middle section of a card
|
||||||
|
let middleSection = document.createElement("div")
|
||||||
|
middleSection.classList = "middleSection"
|
||||||
|
let measurement = document.createElement("span")
|
||||||
|
measurement.classList = "measureValue"
|
||||||
|
measurement.textContent = measure
|
||||||
|
measurement.style.color = col
|
||||||
|
let mbar = document.createElement("span")
|
||||||
|
mbar.textContent = "mbar"
|
||||||
|
mbar.style.fontSize = "2.5em"
|
||||||
|
mbar.style.float = "right"
|
||||||
|
middleSection.appendChild(measurement)
|
||||||
|
middleSection.appendChild(mbar)
|
||||||
|
card.appendChild(middleSection)
|
||||||
|
|
||||||
|
card.appendChild(document.createElement("hr"))
|
||||||
|
|
||||||
|
if (power) {
|
||||||
|
let powerSection = document.createElement("div")
|
||||||
|
let power = document.createElement("span")
|
||||||
|
power.textContent = "Power"
|
||||||
|
let power_toggle = document.createElement("input")
|
||||||
|
power_toggle.type= "checkbox"
|
||||||
|
power_toggle.classList = "switch"
|
||||||
|
powerSection.appendChild(power)
|
||||||
|
powerSection.appendChild(power_toggle)
|
||||||
|
card.appendChild(powerSection)
|
||||||
|
}
|
||||||
|
document.querySelector("#sidebar").appendChild(card)
|
||||||
|
}
|
||||||
|
|
||||||
|
var devices = []
|
||||||
|
const nuWebSocket = new WebSocket("ws://" + window.location.host + "/echo")
|
||||||
|
nuWebSocket.onmessage = (event) => {
|
||||||
|
console.log(event.data)
|
||||||
|
const msg = JSON.parse(event.data)
|
||||||
|
if (msg.devices) {
|
||||||
|
msg.devices.sensor.forEach((device) => {
|
||||||
|
devices.push(device.name)
|
||||||
|
drawCard(device.name, device.color, "---", (device.sensorType == "mks") )
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (msg.measurement) {
|
||||||
|
if (msg.measurement.value) {
|
||||||
|
document.querySelector("#measure").classList.add("pressedButton")
|
||||||
|
}
|
||||||
|
else document.querySelector("#measure").classList.remove("pressedButton")
|
||||||
|
}
|
||||||
|
if (msg.storing) {
|
||||||
|
if (msg.storing.value) {
|
||||||
|
document.querySelector("#store").classList.add("pressedButton")
|
||||||
|
}
|
||||||
|
else document.querySelector("#store").classList.remove("pressedButton")
|
||||||
|
}
|
||||||
|
if (msg.log) {
|
||||||
|
if (msg.log.value) {
|
||||||
|
document.querySelector("#logBar").innerHTML += msg.log.value + "<br>"
|
||||||
|
document.querySelector("#logBar").scrollTo(0, document.querySelector("#logBar").scrollHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.sensor) {
|
||||||
|
for (sensor in msg.sensor) {
|
||||||
|
let sensorButton = document.querySelector("#" + sensor)
|
||||||
|
if (sensorButton) {
|
||||||
|
sensorButton.checked = msg.sensor[sensor]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
fetch("/api/devices")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => {
|
||||||
|
console.log(json)
|
||||||
|
json.sensor.forEach((device) => {
|
||||||
|
devices.push(device.name)
|
||||||
|
drawCard(device.name, device.color, "---", (device.sensorType == "mks") )
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
var data = []
|
||||||
|
|
||||||
|
var layout = {
|
||||||
|
xaxis: {
|
||||||
|
title: 'timestamp'
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: 'pressure (mbar)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Plotly.newPlot('plot', data, layout, { responsive: true });
|
||||||
|
|
||||||
|
document.querySelector("#measure").addEventListener("click", (e) => {
|
||||||
|
nuWebSocket.send("measurePressed")
|
||||||
|
console.log("measurePressed")
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelector("#store").addEventListener("click", (e) => {
|
||||||
|
nuWebSocket.send("storePressed")
|
||||||
|
console.log("storePressed")
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelector("#log").addEventListener("click", (e) => {
|
||||||
|
console.log("logPressed")
|
||||||
|
document.querySelector("#logBar").hidden = !document.querySelector("#logBar").hidden
|
||||||
|
document.querySelector("#logBar").scrollTo(0, document.querySelector("#logBar").scrollHeight)
|
||||||
|
})
|
||||||
|
|
144
numass-web-control/src/main/resources/styles.css
Normal file
144
numass-web-control/src/main/resources/styles.css
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
.myButton {
|
||||||
|
color: white;
|
||||||
|
background-color: #ad0022;
|
||||||
|
border: 1px black solid;
|
||||||
|
padding: 0.2em 1ch;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myButton:hover {
|
||||||
|
background-color: #7b0d23;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pressedButton {
|
||||||
|
background-color: #008a03;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testButton {
|
||||||
|
color: magenta;
|
||||||
|
background: cyan;
|
||||||
|
}
|
||||||
|
#log {
|
||||||
|
color: magenta;
|
||||||
|
background: cyan;
|
||||||
|
float: right;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
#plot {
|
||||||
|
width: 80%;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #f6e9ff;
|
||||||
|
font-family: Sans-Serif;
|
||||||
|
}
|
||||||
|
#lower {
|
||||||
|
flex: 4;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#logBar {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
background: grey;
|
||||||
|
border: 1px black solid;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.5em;
|
||||||
|
max-height: 20%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.upperSection {
|
||||||
|
background: lightblue;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#sidebar {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 3em);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 1px black solid;
|
||||||
|
padding: .25em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.switch {
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
left: -1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.switch:before {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 3em;
|
||||||
|
height: 1em;
|
||||||
|
background: grey;
|
||||||
|
left: -1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input.switch:after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
background: #fff;
|
||||||
|
left: -1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
transition: .25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.switch:checked:after {
|
||||||
|
left: 1em;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.switch:checked:before {
|
||||||
|
background: #bada55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton {
|
||||||
|
border-radius: 1em;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
padding: 0.5em;
|
||||||
|
height: 3em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.vl {
|
||||||
|
border-left: 1px solid grey;
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.measureValue {
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.middleSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 1ch;
|
||||||
|
}
|
@ -53,4 +53,4 @@ include("numass-core:numass-data-proto")
|
|||||||
include("numass-core:numass-signal-processing")
|
include("numass-core:numass-signal-processing")
|
||||||
|
|
||||||
include(":numass-viewer")
|
include(":numass-viewer")
|
||||||
|
include("numass-web-control")
|
||||||
|
Loading…
Reference in New Issue
Block a user