forked from kscience/visionforge
Refactoring and UI cleanup
This commit is contained in:
parent
2d79e8e422
commit
aca1a8af78
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,11 @@ inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagCo
|
||||
}
|
||||
|
||||
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) {
|
||||
div("card w-100") {
|
||||
div("card w-100 h-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
h3(classes = "card-title") {
|
||||
+title
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
||||
@ -78,7 +80,7 @@ fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card") {
|
||||
div("card p-0 m-0") {
|
||||
div("card-header") {
|
||||
attrs {
|
||||
this.id = headerID
|
||||
|
@ -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> {
|
||||
val pair = react.useState(init)
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
|
@ -2,14 +2,12 @@ package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.js.RFBuilder
|
||||
import hep.dataforge.js.component
|
||||
import hep.dataforge.js.initState
|
||||
import hep.dataforge.js.memoize
|
||||
import hep.dataforge.js.state
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Value
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
@ -41,15 +39,15 @@ interface ConfigEditorProps : RProps {
|
||||
}
|
||||
|
||||
private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
var expanded: Boolean by initState { true }
|
||||
val item = memoize(props.root, props.name) { props.root[props.name] }
|
||||
val descriptorItem: ItemDescriptor? = memoize(props.descriptor, props.name) { props.descriptor?.get(props.name) }
|
||||
val defaultItem = memoize(props.default, props.name) { props.default?.get(props.name) }
|
||||
var expanded: Boolean by state { true }
|
||||
val item = props.root[props.name]
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val defaultItem = props.default?.get(props.name)
|
||||
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
||||
|
||||
val token = props.name.last()?.toString() ?: "Properties"
|
||||
|
||||
var kostyl by initState { false }
|
||||
var kostyl by state { false }
|
||||
|
||||
fun update() {
|
||||
kostyl = !kostyl
|
||||
@ -73,20 +71,6 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
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) {
|
||||
is MetaItem.NodeItem -> {
|
||||
div {
|
||||
@ -127,9 +111,8 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
div {
|
||||
div("row") {
|
||||
div("col") {
|
||||
p("tree-label") {
|
||||
div("d-flex flex-row align-items-center") {
|
||||
div("flex-grow-1 p-1 mr-auto tree-label") {
|
||||
+token
|
||||
attrs {
|
||||
if (item == null) {
|
||||
@ -137,16 +120,10 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
div("d-inline-flex") {
|
||||
valueChooser(props.root, props.name, actualItem.value, descriptorItem as? ValueDescriptor)
|
||||
}
|
||||
div("col") {
|
||||
child(ValueChooser) {
|
||||
attrs {
|
||||
this.value = actualItem.value
|
||||
this.descriptor = descriptorItem as? ValueDescriptor
|
||||
this.valueChanged = valueChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
div("d-inline-flex p-1") {
|
||||
button(classes = "btn btn-link") {
|
||||
+"\u00D7"
|
||||
attrs {
|
||||
@ -157,7 +134,7 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package hep.dataforge.vis.editor
|
||||
import hep.dataforge.js.RFBuilder
|
||||
import hep.dataforge.js.card
|
||||
import hep.dataforge.js.component
|
||||
import hep.dataforge.js.initState
|
||||
import hep.dataforge.js.state
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.startsWith
|
||||
@ -29,7 +29,7 @@ interface TreeState : RState {
|
||||
}
|
||||
|
||||
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 = {
|
||||
expanded = !expanded
|
||||
|
@ -1,9 +1,11 @@
|
||||
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.get
|
||||
import hep.dataforge.meta.setValue
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.*
|
||||
import hep.dataforge.vis.widgetType
|
||||
import kotlinx.html.InputType
|
||||
@ -11,51 +13,10 @@ import kotlinx.html.js.onChangeFunction
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.RProps
|
||||
import react.RState
|
||||
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
|
||||
import react.RBuilder
|
||||
import react.dom.*
|
||||
|
||||
internal fun RBuilder.valueChooser(root: Config, name: Name, value: Value, descriptor: ValueDescriptor?) {
|
||||
val onValueChange: (Event) -> Unit = {
|
||||
val res = when (val t = it.target) {
|
||||
// (it.target as HTMLInputElement).value
|
||||
@ -67,22 +28,26 @@ val ValueChooser = component<ValueChooserProps> { props ->
|
||||
is HTMLSelectElement -> t.value.asValue()
|
||||
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()
|
||||
when {
|
||||
type == ValueType.BOOLEAN -> {
|
||||
input(type = InputType.checkBox, classes = "float-right") {
|
||||
input(type = InputType.checkBox) {
|
||||
attrs {
|
||||
checked = props.value.boolean
|
||||
checked = value.boolean
|
||||
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 {
|
||||
descriptor.attributes["step"].string?.let {
|
||||
step = it
|
||||
@ -93,11 +58,11 @@ val ValueChooser = component<ValueChooserProps> { props ->
|
||||
descriptor.attributes["max"].string?.let {
|
||||
max = it
|
||||
}
|
||||
this.value = props.value.string
|
||||
this.defaultValue = value.string
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> select("float-right") {
|
||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> select (classes = "w-100") {
|
||||
descriptor!!.allowedValues.forEach {
|
||||
option {
|
||||
+it.string
|
||||
@ -108,19 +73,18 @@ val ValueChooser = component<ValueChooserProps> { props ->
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
descriptor?.widgetType == "color" -> input(type = InputType.color, classes = "float-right") {
|
||||
descriptor?.widgetType == "color" -> input(type = InputType.color) {
|
||||
attrs {
|
||||
this.value = props.value.string
|
||||
this.value = value.string
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
else -> input(type = InputType.text, classes = "float-right") {
|
||||
else -> input(type = InputType.text, classes = "form-control w-100") {
|
||||
attrs {
|
||||
this.value = props.value.string
|
||||
this.value = value.string
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -56,14 +56,12 @@ class Material3D : Scheme() {
|
||||
defineValue(OPACITY_KEY) {
|
||||
type(ValueType.NUMBER)
|
||||
default(1.0)
|
||||
configure {
|
||||
"attributes" to {
|
||||
config["attributes"] = Meta {
|
||||
this["min"] = 0.0
|
||||
this["max"] = 1.0
|
||||
this["step"] = 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
defineValue(WIREFRAME_KEY) {
|
||||
type(ValueType.BOOLEAN)
|
||||
default(false)
|
||||
|
@ -68,18 +68,13 @@ interface VisualObject3D : VisualObject {
|
||||
default(true)
|
||||
}
|
||||
|
||||
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
|
||||
|
||||
//TODO replace by descriptor merge
|
||||
defineValue(VisualObject.STYLE_KEY){
|
||||
type(ValueType.STRING)
|
||||
multiple = true
|
||||
}
|
||||
|
||||
// Material3D.MATERIAL_COLOR_KEY put "#ffffff"
|
||||
// Material3D.MATERIAL_OPACITY_KEY put 1.0
|
||||
// Material3D.MATERIAL_WIREFRAME_KEY put false
|
||||
|
||||
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,10 +109,10 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
|
||||
|
||||
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 = {
|
||||
renderer.setSize(element.offsetWidth, element.offsetWidth)
|
||||
window.onresize = {
|
||||
renderer.setSize(element.clientWidth, element.clientWidth)
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package ru.mipt.npm.muon.monitor
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.js.card
|
||||
import hep.dataforge.js.component
|
||||
import hep.dataforge.js.initState
|
||||
import hep.dataforge.js.state
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.isEmpty
|
||||
@ -40,17 +40,21 @@ private val canvasConfig = Canvas {
|
||||
}
|
||||
|
||||
val MMApp = component<MMAppProps> { props ->
|
||||
var selected by initState { props.selected }
|
||||
var canvas: ThreeCanvas? by initState { null }
|
||||
var selected by state { props.selected }
|
||||
var canvas: ThreeCanvas? by state { null }
|
||||
|
||||
val select: (Name?) -> Unit = {
|
||||
selected = it
|
||||
}
|
||||
|
||||
val visual = props.model.root
|
||||
|
||||
div("row") {
|
||||
div("col-lg-3") {
|
||||
h1("mx-auto") {
|
||||
+"Muon monitor demo"
|
||||
}
|
||||
}
|
||||
div("row") {
|
||||
div("col-lg-3 mh-100 px-0") {
|
||||
//tree
|
||||
card("Object tree") {
|
||||
objectTree(visual, selected, select)
|
||||
@ -156,5 +160,6 @@ val MMApp = component<MMAppProps> { props ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -11,25 +11,6 @@
|
||||
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
|
||||
<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>
|
||||
<div class="container-fluid" id = "app"> </div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user