Refactoring and UI cleanup

This commit is contained in:
Alexander Nozik 2020-04-23 14:09:56 +03:00
parent 2d79e8e422
commit aca1a8af78
13 changed files with 184 additions and 151 deletions

View File

@ -0,0 +1,24 @@
package hep.dataforge.properties
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.get
import hep.dataforge.names.Name
class ConfigProperty(val config: Config, val name: Name) : Property<MetaItem<*>?> {
override var value: MetaItem<*>?
get() = config[name]
set(value) {
config[name] = value
}
override fun onChange(owner: Any?, callback: (MetaItem<*>?) -> Unit) {
config.onChange(owner) { name, oldItem, newItem ->
if (name == this.name && oldItem != newItem) callback(newItem)
}
}
override fun removeChangeListener(owner: Any?) {
config.removeListener(owner)
}
}

View File

@ -0,0 +1,57 @@
package hep.dataforge.properties
import hep.dataforge.meta.DFExperimental
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
//TODO move to core
interface Property<T> {
var value: T
fun onChange(owner: Any? = null, callback: (T) -> Unit)
fun removeChangeListener(owner: Any? = null)
}
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> Property<T>.flow() = callbackFlow<T> {
send(value)
onChange(this) {
//TODO add exception handler?
launch {
send(it)
}
}
awaitClose { removeChangeListener(this) }
}
/**
* Reflect all changes in the [source] property onto this property
*
* @return a mirroring job
*/
fun <T> Property<T>.mirror(source: Property<T>, scope: CoroutineScope): Job {
return scope.launch {
source.flow().collect {
value = it
}
}
}
/**
* Bi-directional connection between properties
*/
@DFExperimental
fun <T> Property<T>.bind(other: Property<T>) {
onChange(other) {
other.value = it
}
other.onChange {
this.value = it
}
}

View File

@ -0,0 +1,30 @@
package hep.dataforge.js
import hep.dataforge.properties.Property
import org.w3c.dom.HTMLInputElement
fun HTMLInputElement.bindValue(property: Property<String>) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.value
Unit
}
property.onChange(this) {
if (value != it) {
value = it
}
}
}
fun HTMLInputElement.bindChecked(property: Property<Boolean>) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.checked
Unit
}
property.onChange(this) {
if (checked != it) {
checked = it
}
}
}

View File

@ -16,9 +16,11 @@ inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagCo
} }
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) { inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) {
div("card w-100") { div("card w-100 h-100") {
div("card-body") { div("card-body") {
h3(classes = "card-title") { +title } h3(classes = "card-title") {
+title
}
block() block()
} }
} }
@ -78,7 +80,7 @@ fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.
elements.forEachIndexed { index, (title, builder) -> elements.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading" val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse" val collapseID = "${id}-${index}-collapse"
div("card") { div("card p-0 m-0") {
div("card-header") { div("card-header") {
attrs { attrs {
this.id = headerID this.id = headerID

View File

@ -22,7 +22,7 @@ fun <P : RProps> component(
} }
} }
fun <T> RFBuilder.initState(init: () -> T): ReadWriteProperty<Any?, T> = fun <T> RFBuilder.state(init: () -> T): ReadWriteProperty<Any?, T> =
object : ReadWriteProperty<Any?, T> { object : ReadWriteProperty<Any?, T> {
val pair = react.useState(init) val pair = react.useState(init)
override fun getValue(thisRef: Any?, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {

View File

@ -2,14 +2,12 @@ package hep.dataforge.vis.editor
import hep.dataforge.js.RFBuilder import hep.dataforge.js.RFBuilder
import hep.dataforge.js.component import hep.dataforge.js.component
import hep.dataforge.js.initState import hep.dataforge.js.state
import hep.dataforge.js.memoize
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.* import hep.dataforge.meta.descriptors.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.values.Value
import kotlinx.html.classes import kotlinx.html.classes
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element import org.w3c.dom.Element
@ -41,15 +39,15 @@ interface ConfigEditorProps : RProps {
} }
private fun RFBuilder.configEditorItem(props: ConfigEditorProps) { private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
var expanded: Boolean by initState { true } var expanded: Boolean by state { true }
val item = memoize(props.root, props.name) { props.root[props.name] } val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = memoize(props.descriptor, props.name) { props.descriptor?.get(props.name) } val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val defaultItem = memoize(props.default, props.name) { props.default?.get(props.name) } val defaultItem = props.default?.get(props.name)
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem() val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val token = props.name.last()?.toString() ?: "Properties" val token = props.name.last()?.toString() ?: "Properties"
var kostyl by initState { false } var kostyl by state { false }
fun update() { fun update() {
kostyl = !kostyl kostyl = !kostyl
@ -73,20 +71,6 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
update() update()
} }
val valueChanged: (Value?) -> Unit = { value ->
try {
if (value == null) {
props.root.remove(props.name)
} else {
props.root.setValue(props.name, value)
}
update()
} catch (ex: Exception) {
console.error("Can't set config property ${props.name} to $value")
}
}
when (actualItem) { when (actualItem) {
is MetaItem.NodeItem -> { is MetaItem.NodeItem -> {
div { div {
@ -127,37 +111,30 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
} }
is MetaItem.ValueItem -> { is MetaItem.ValueItem -> {
div { div {
div("row") { div("d-flex flex-row align-items-center") {
div("col") { div("flex-grow-1 p-1 mr-auto tree-label") {
p("tree-label") { +token
+token attrs {
if (item == null) {
classes += "tree-label-inactive"
}
}
}
div("d-inline-flex") {
valueChooser(props.root, props.name, actualItem.value, descriptorItem as? ValueDescriptor)
}
div("d-inline-flex p-1") {
button(classes = "btn btn-link") {
+"\u00D7"
attrs { attrs {
if (item == null) { if (item == null) {
classes += "tree-label-inactive" disabled = true
} else {
onClickFunction = removeClick
} }
} }
} }
} }
div("col") {
child(ValueChooser) {
attrs {
this.value = actualItem.value
this.descriptor = descriptorItem as? ValueDescriptor
this.valueChanged = valueChanged
}
}
}
button(classes = "btn btn-link") {
+"\u00D7"
attrs {
if (item == null) {
disabled = true
} else {
onClickFunction = removeClick
}
}
}
} }
} }
} }

View File

@ -3,7 +3,7 @@ package hep.dataforge.vis.editor
import hep.dataforge.js.RFBuilder import hep.dataforge.js.RFBuilder
import hep.dataforge.js.card import hep.dataforge.js.card
import hep.dataforge.js.component import hep.dataforge.js.component
import hep.dataforge.js.initState import hep.dataforge.js.state
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
@ -29,7 +29,7 @@ interface TreeState : RState {
} }
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by initState{ props.selected?.startsWith(props.name) ?: false } var expanded: Boolean by state{ props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = { val onClick: (Event) -> Unit = {
expanded = !expanded expanded = !expanded

View File

@ -1,9 +1,11 @@
package hep.dataforge.vis.editor package hep.dataforge.vis.editor
import hep.dataforge.js.component import hep.dataforge.meta.Config
import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.setValue
import hep.dataforge.meta.string import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.values.* import hep.dataforge.values.*
import hep.dataforge.vis.widgetType import hep.dataforge.vis.widgetType
import kotlinx.html.InputType import kotlinx.html.InputType
@ -11,51 +13,10 @@ import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.RProps import react.RBuilder
import react.RState import react.dom.*
import react.dom.div
import react.dom.input
import react.dom.option
import react.dom.select
interface ValueChooserProps : RProps {
var value: Value
var descriptor: ValueDescriptor?
var valueChanged: (Value?) -> Unit
}
interface ValueChooserState : RState {
var value: Value
}
//class TextValueChooser(props: ValueChooserProps) : RComponent<ValueChooserProps, ValueChooserState>(props) {
//
// override fun ValueChooserState.init(props: ValueChooserProps) {
// this.value = props.value
// }
//
// val valueChanged: (Event) -> Unit = {
// val res = (it.target as HTMLInputElement).value.asValue()
// setState {
// this.value = res
// }
// props.valueChanged(res)
// }
//
// override fun RBuilder.render() {
// input(type = InputType.text, classes = "float-right") {
// attrs {
// this.value = state.value.string
// onChangeFunction = valueChanged
// }
// }
// }
//}
val ValueChooser = component<ValueChooserProps> { props ->
// var state by initState {props.value }
val descriptor = props.descriptor
internal fun RBuilder.valueChooser(root: Config, name: Name, value: Value, descriptor: ValueDescriptor?) {
val onValueChange: (Event) -> Unit = { val onValueChange: (Event) -> Unit = {
val res = when (val t = it.target) { val res = when (val t = it.target) {
// (it.target as HTMLInputElement).value // (it.target as HTMLInputElement).value
@ -67,22 +28,26 @@ val ValueChooser = component<ValueChooserProps> { props ->
is HTMLSelectElement -> t.value.asValue() is HTMLSelectElement -> t.value.asValue()
else -> error("Unknown event target: $t") else -> error("Unknown event target: $t")
} }
// state = res
props.valueChanged(res) try {
root.setValue(name, res)
} catch (ex: Exception) {
console.error("Can't set config property ${name} to $res")
}
} }
div { div() {
val type = descriptor?.type?.firstOrNull() val type = descriptor?.type?.firstOrNull()
when { when {
type == ValueType.BOOLEAN -> { type == ValueType.BOOLEAN -> {
input(type = InputType.checkBox, classes = "float-right") { input(type = InputType.checkBox) {
attrs { attrs {
checked = props.value.boolean checked = value.boolean
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
} }
} }
type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") { type == ValueType.NUMBER -> input(type = InputType.number, classes = "form-control w-100") {
attrs { attrs {
descriptor.attributes["step"].string?.let { descriptor.attributes["step"].string?.let {
step = it step = it
@ -93,11 +58,11 @@ val ValueChooser = component<ValueChooserProps> { props ->
descriptor.attributes["max"].string?.let { descriptor.attributes["max"].string?.let {
max = it max = it
} }
this.value = props.value.string this.defaultValue = value.string
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
} }
descriptor?.allowedValues?.isNotEmpty() ?: false -> select("float-right") { descriptor?.allowedValues?.isNotEmpty() ?: false -> select (classes = "w-100") {
descriptor!!.allowedValues.forEach { descriptor!!.allowedValues.forEach {
option { option {
+it.string +it.string
@ -108,19 +73,18 @@ val ValueChooser = component<ValueChooserProps> { props ->
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
} }
descriptor?.widgetType == "color" -> input(type = InputType.color, classes = "float-right") { descriptor?.widgetType == "color" -> input(type = InputType.color) {
attrs { attrs {
this.value = props.value.string this.value = value.string
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
} }
else -> input(type = InputType.text, classes = "float-right") { else -> input(type = InputType.text, classes = "form-control w-100") {
attrs { attrs {
this.value = props.value.string this.value = value.string
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
} }
} }
} }
} }

View File

@ -56,12 +56,10 @@ class Material3D : Scheme() {
defineValue(OPACITY_KEY) { defineValue(OPACITY_KEY) {
type(ValueType.NUMBER) type(ValueType.NUMBER)
default(1.0) default(1.0)
configure { config["attributes"] = Meta {
"attributes" to { this["min"] = 0.0
this["min"] = 0.0 this["max"] = 1.0
this["max"] = 1.0 this["step"] = 0.1
this["step"] = 0.1
}
} }
} }
defineValue(WIREFRAME_KEY) { defineValue(WIREFRAME_KEY) {

View File

@ -68,18 +68,13 @@ interface VisualObject3D : VisualObject {
default(true) default(true)
} }
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
//TODO replace by descriptor merge //TODO replace by descriptor merge
defineValue(VisualObject.STYLE_KEY){ defineValue(VisualObject.STYLE_KEY){
type(ValueType.STRING) type(ValueType.STRING)
multiple = true multiple = true
} }
// Material3D.MATERIAL_COLOR_KEY put "#ffffff" defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
// Material3D.MATERIAL_OPACITY_KEY put 1.0
// Material3D.MATERIAL_WIREFRAME_KEY put false
} }
} }
} }

View File

@ -109,10 +109,10 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
element.appendChild(renderer.domElement) element.appendChild(renderer.domElement)
renderer.setSize(max(canvas.minSize, element.offsetWidth), max(canvas.minSize, element.offsetWidth)) renderer.setSize(max(canvas.minSize, element.clientWidth), max(canvas.minSize, element.clientWidth))
element.onresize = { window.onresize = {
renderer.setSize(element.offsetWidth, element.offsetWidth) renderer.setSize(element.clientWidth, element.clientWidth)
camera.updateProjectionMatrix() camera.updateProjectionMatrix()
} }

View File

@ -3,7 +3,7 @@ package ru.mipt.npm.muon.monitor
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.js.card import hep.dataforge.js.card
import hep.dataforge.js.component import hep.dataforge.js.component
import hep.dataforge.js.initState import hep.dataforge.js.state
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
@ -40,17 +40,21 @@ private val canvasConfig = Canvas {
} }
val MMApp = component<MMAppProps> { props -> val MMApp = component<MMAppProps> { props ->
var selected by initState { props.selected } var selected by state { props.selected }
var canvas: ThreeCanvas? by initState { null } var canvas: ThreeCanvas? by state { null }
val select: (Name?) -> Unit = { val select: (Name?) -> Unit = {
selected = it selected = it
} }
val visual = props.model.root val visual = props.model.root
div("row") { div("row") {
div("col-lg-3") { h1("mx-auto") {
+"Muon monitor demo"
}
}
div("row") {
div("col-lg-3 mh-100 px-0") {
//tree //tree
card("Object tree") { card("Object tree") {
objectTree(visual, selected, select) objectTree(visual, selected, select)
@ -156,5 +160,6 @@ val MMApp = component<MMAppProps> { props ->
} }
} }
} }
} }
} }

View File

@ -11,25 +11,6 @@
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script> <script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
</head> </head>
<body class="testApp"> <body class="testApp">
<div class="container-fluid" id = "app"> </div>
<div class="container">
<h1>Muon monitor demo</h1>
</div>
<div class="container-fluid">
<div id="app"></div>
<!-- <div class="row">
<div class="col-lg-3">
<div class="row" id="tree"></div>
</div>
<div class="col-lg-6">
<div id="canvas"></div>
</div>
<div class="col-lg-3">
<div class="row" id="settings"></div>
<div class="row" id="editor"></div>
</div>
</div>-->
</div>
</body> </body>
</html> </html>