forked from kscience/visionforge
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
103fbe94ae
@ -6,6 +6,10 @@ val dataforgeVersion: String by rootProject.extra
|
|||||||
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
|
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
js {
|
||||||
|
useCommonJs()
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package hep.dataforge.vis
|
package hep.dataforge.vis
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
import hep.dataforge.vis.VisualObject.Companion.STYLE_KEY
|
import hep.dataforge.vis.VisualObject.Companion.STYLE_KEY
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
@ -19,15 +21,15 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
|
|
||||||
protected abstract var properties: Config?
|
protected abstract var properties: Config?
|
||||||
|
|
||||||
override var styles: List<String>
|
final override var styles: List<String>
|
||||||
get() = properties?.get(STYLE_KEY).stringList
|
get() = properties?.get(STYLE_KEY).stringList
|
||||||
set(value) {
|
set(value) {
|
||||||
//val allStyles = (field + value).distinct()
|
|
||||||
setProperty(STYLE_KEY, Value.of(value))
|
setProperty(STYLE_KEY, Value.of(value))
|
||||||
updateStyles(value)
|
updateStyles(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun updateStyles(names: List<String>) {
|
protected fun updateStyles(names: List<String>) {
|
||||||
|
styleCache = null
|
||||||
names.mapNotNull { findStyle(it) }.asSequence()
|
names.mapNotNull { findStyle(it) }.asSequence()
|
||||||
.flatMap { it.items.asSequence() }
|
.flatMap { it.items.asSequence() }
|
||||||
.distinctBy { it.key }
|
.distinctBy { it.key }
|
||||||
@ -77,7 +79,7 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
/**
|
/**
|
||||||
* All available properties in a layered form
|
* All available properties in a layered form
|
||||||
*/
|
*/
|
||||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, parent?.allProperties())
|
||||||
|
|
||||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||||
return if (inherit) {
|
return if (inherit) {
|
||||||
@ -86,6 +88,16 @@ abstract class AbstractVisualObject : VisualObject {
|
|||||||
properties?.get(name) ?: mergedStyles[name]
|
properties?.get(name) ?: mergedStyles[name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val descriptor = NodeDescriptor {
|
||||||
|
defineValue(STYLE_KEY){
|
||||||
|
type(ValueType.STRING)
|
||||||
|
multiple = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||||
|
@ -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) {
|
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
|
||||||
|
@ -1,10 +1,28 @@
|
|||||||
package hep.dataforge.js
|
package hep.dataforge.js
|
||||||
|
|
||||||
import react.RBuilder
|
import react.*
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
fun <T> RBuilder.initState(init: () -> T): ReadWriteProperty<Any?, T> =
|
class RFBuilder : RBuilder()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get functional component from [func]
|
||||||
|
*/
|
||||||
|
fun <P : RProps> component(
|
||||||
|
func: RFBuilder.(props: P) -> Unit
|
||||||
|
): FunctionalComponent<P> {
|
||||||
|
return { props: P ->
|
||||||
|
val nodes = RFBuilder().apply { func(props) }.childList
|
||||||
|
when (nodes.size) {
|
||||||
|
0 -> null
|
||||||
|
1 -> nodes.first()
|
||||||
|
else -> createElement(Fragment, kotlinext.js.js {}, *nodes.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -16,3 +34,5 @@ fun <T> RBuilder.initState(init: () -> T): ReadWriteProperty<Any?, T> =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> RFBuilder.memoize(vararg deps: dynamic, builder: () -> T): T = useMemo(builder, deps)
|
||||||
|
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
package hep.dataforge.vis.editor
|
package hep.dataforge.vis.editor
|
||||||
|
|
||||||
|
import hep.dataforge.js.RFBuilder
|
||||||
|
import hep.dataforge.js.component
|
||||||
|
import hep.dataforge.js.state
|
||||||
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.*
|
|
||||||
import hep.dataforge.vis.widgetType
|
|
||||||
import kotlinx.html.ButtonType
|
|
||||||
import kotlinx.html.InputType
|
|
||||||
import kotlinx.html.classes
|
import kotlinx.html.classes
|
||||||
import kotlinx.html.js.onChangeFunction
|
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLInputElement
|
|
||||||
import org.w3c.dom.HTMLSelectElement
|
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
import react.RBuilder
|
import react.*
|
||||||
import react.RComponent
|
|
||||||
import react.RProps
|
|
||||||
import react.dom.*
|
import react.dom.*
|
||||||
import react.setState
|
|
||||||
|
|
||||||
interface ConfigEditorProps : RProps {
|
interface ConfigEditorProps : RProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root config object - always non null
|
* Root config object - always non null
|
||||||
*/
|
*/
|
||||||
@ -42,129 +36,83 @@ interface ConfigEditorProps : RProps {
|
|||||||
* Root descriptor
|
* Root descriptor
|
||||||
*/
|
*/
|
||||||
var descriptor: NodeDescriptor?
|
var descriptor: NodeDescriptor?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||||
|
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()
|
||||||
|
|
||||||
override fun TreeState.init() {
|
val token = props.name.last()?.toString() ?: "Properties"
|
||||||
expanded = true
|
|
||||||
|
var kostyl by state { false }
|
||||||
|
|
||||||
|
fun update() {
|
||||||
|
kostyl = !kostyl
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun componentDidMount() {
|
useEffectWithCleanup(listOf(props.root)) {
|
||||||
props.root.onChange(this) { name, _, _ ->
|
props.root.onChange(this) { name, _, _ ->
|
||||||
if (name == props.name) {
|
if (name == props.name) {
|
||||||
forceUpdate()
|
update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return@useEffectWithCleanup { props.root.removeListener(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun componentWillUnmount() {
|
val expanderClick: (Event) -> Unit = {
|
||||||
props.root.removeListener(this)
|
expanded = !expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onClick: (Event) -> Unit = {
|
val removeClick: (Event) -> Unit = {
|
||||||
setState {
|
|
||||||
expanded = !expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onValueChange: (Event) -> Unit = {
|
|
||||||
val value = when (val t = it.target) {
|
|
||||||
// (it.target as HTMLInputElement).value
|
|
||||||
is HTMLInputElement -> if (t.type == "checkbox") {
|
|
||||||
if (t.checked) True else False
|
|
||||||
} else {
|
|
||||||
t.value.asValue()
|
|
||||||
}
|
|
||||||
is HTMLSelectElement -> t.value.asValue()
|
|
||||||
else -> error("Unknown event target: $t")
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
props.root.setValue(props.name, value)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
console.error("Can't set config property ${props.name} to $value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val removeValue: (Event) -> Unit = {
|
|
||||||
props.root.remove(props.name)
|
props.root.remove(props.name)
|
||||||
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO replace by separate components
|
when (actualItem) {
|
||||||
private fun RBuilder.valueChooser(value: Value, descriptor: ValueDescriptor?) {
|
is MetaItem.NodeItem -> {
|
||||||
val type = descriptor?.type?.firstOrNull()
|
div {
|
||||||
when {
|
span("tree-caret") {
|
||||||
type == ValueType.BOOLEAN -> {
|
|
||||||
input(type = InputType.checkBox, classes = "float-right") {
|
|
||||||
attrs {
|
attrs {
|
||||||
defaultChecked = value.boolean
|
if (expanded) {
|
||||||
onChangeFunction = onValueChange
|
classes += "tree-caret-down"
|
||||||
|
}
|
||||||
|
onClickFunction = expanderClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
span("tree-label") {
|
||||||
type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") {
|
+token
|
||||||
attrs {
|
attrs {
|
||||||
descriptor.attributes["step"].string?.let {
|
if (item == null) {
|
||||||
step = it
|
classes += "tree-label-inactive"
|
||||||
}
|
|
||||||
descriptor.attributes["min"].string?.let {
|
|
||||||
min = it
|
|
||||||
}
|
|
||||||
descriptor.attributes["max"].string?.let {
|
|
||||||
max = it
|
|
||||||
}
|
|
||||||
defaultValue = value.string
|
|
||||||
onChangeFunction = onValueChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> select("float-right") {
|
|
||||||
descriptor!!.allowedValues.forEach {
|
|
||||||
option {
|
|
||||||
+it.string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attrs {
|
|
||||||
multiple = false
|
|
||||||
onChangeFunction = onValueChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
descriptor?.widgetType == "color" -> input(type = InputType.color, classes = "float-right") {
|
|
||||||
attrs {
|
|
||||||
defaultValue = value.string
|
|
||||||
onChangeFunction = onValueChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> input(type = InputType.text, classes = "float-right") {
|
|
||||||
attrs {
|
|
||||||
defaultValue = value.string
|
|
||||||
onChangeFunction = onValueChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun RBuilder.render() {
|
|
||||||
val item = props.root[props.name]
|
|
||||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
|
||||||
val defaultItem = props.default?.get(props.name)
|
|
||||||
val actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
|
||||||
val token = props.name.last()?.toString() ?: "Properties"
|
|
||||||
|
|
||||||
when (actualItem) {
|
|
||||||
is MetaItem.NodeItem -> {
|
|
||||||
div {
|
|
||||||
span("tree-caret") {
|
|
||||||
attrs {
|
|
||||||
if (state.expanded) {
|
|
||||||
classes += "tree-caret-down"
|
|
||||||
}
|
|
||||||
onClickFunction = onClick
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
span("tree-label") {
|
}
|
||||||
|
}
|
||||||
|
if (expanded) {
|
||||||
|
ul("tree") {
|
||||||
|
val keys = buildSet<NameToken> {
|
||||||
|
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
|
||||||
|
add(NameToken(it))
|
||||||
|
}
|
||||||
|
item?.node?.items?.keys?.let { addAll(it) }
|
||||||
|
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.forEach { token ->
|
||||||
|
li("tree-item align-middle") {
|
||||||
|
configEditor(props.root, props.name + token, props.descriptor, props.default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MetaItem.ValueItem -> {
|
||||||
|
div {
|
||||||
|
div("d-flex flex-row align-items-center") {
|
||||||
|
div("flex-grow-1 p-1 mr-auto tree-label") {
|
||||||
+token
|
+token
|
||||||
attrs {
|
attrs {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
@ -172,109 +120,51 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
div("d-inline-flex") {
|
||||||
if (state.expanded) {
|
valueChooser(props.root, props.name, actualItem.value, descriptorItem as? ValueDescriptor)
|
||||||
ul("tree") {
|
}
|
||||||
val keys = buildSet<NameToken> {
|
div("d-inline-flex p-1") {
|
||||||
item?.node?.items?.keys?.let { addAll(it) }
|
button(classes = "btn btn-link") {
|
||||||
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
+"\u00D7"
|
||||||
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
|
attrs {
|
||||||
add(NameToken(it))
|
if (item == null) {
|
||||||
}
|
disabled = true
|
||||||
}
|
} else {
|
||||||
|
onClickFunction = removeClick
|
||||||
keys.forEach { token ->
|
|
||||||
li("tree-item") {
|
|
||||||
child(ConfigEditorComponent::class) {
|
|
||||||
attrs {
|
|
||||||
this.root = props.root
|
|
||||||
this.name = props.name + token
|
|
||||||
this.default = props.default
|
|
||||||
this.descriptor = props.descriptor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MetaItem.ValueItem -> {
|
}
|
||||||
div {
|
}
|
||||||
div("row") {
|
}
|
||||||
div("col") {
|
|
||||||
p("tree-label") {
|
|
||||||
+token
|
|
||||||
attrs {
|
|
||||||
if (item == null) {
|
|
||||||
classes += "tree-label-inactive"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div("col") {
|
|
||||||
valueChooser(actualItem.value, descriptorItem as? ValueDescriptor)
|
|
||||||
}
|
|
||||||
div("col-auto") {
|
|
||||||
div("dropleft p-0") {
|
|
||||||
button(classes = "btn btn-outline-primary") {
|
|
||||||
attrs {
|
|
||||||
type = ButtonType.button
|
|
||||||
attributes["data-toggle"] = "dropdown"
|
|
||||||
attributes["aria-haspopup"] = "true"
|
|
||||||
attributes["aria-expanded"] = "false"
|
|
||||||
attributes["data-boundary"] = "viewport"
|
|
||||||
}
|
|
||||||
+"\u22ee"
|
|
||||||
}
|
|
||||||
div(classes = "dropdown-menu") {
|
|
||||||
button(classes = "btn btn-outline dropdown-item") {
|
|
||||||
+"Info"
|
|
||||||
}
|
|
||||||
if (item != null) {
|
|
||||||
button(classes = "btn btn-outline dropdown-item") {
|
|
||||||
+"""Clear"""
|
|
||||||
}
|
|
||||||
attrs {
|
|
||||||
onClickFunction = removeValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = component { configEditorItem(it) }
|
||||||
}
|
|
||||||
}
|
fun RBuilder.configEditor(
|
||||||
}
|
config: Config,
|
||||||
}
|
name: Name = Name.EMPTY,
|
||||||
}
|
descriptor: NodeDescriptor? = null,
|
||||||
|
default: Meta? = null
|
||||||
|
) {
|
||||||
|
child(ConfigEditor) {
|
||||||
|
attrs {
|
||||||
|
this.root = config
|
||||||
|
this.name = name
|
||||||
|
this.descriptor = descriptor
|
||||||
|
this.default = default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
|
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
|
||||||
render(this) {
|
render(this) {
|
||||||
child(ConfigEditorComponent::class) {
|
configEditor(config, Name.EMPTY, descriptor, default)
|
||||||
attrs {
|
|
||||||
root = config
|
|
||||||
name = Name.EMPTY
|
|
||||||
this.descriptor = descriptor
|
|
||||||
this.default = default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
|
|
||||||
div {
|
|
||||||
child(ConfigEditorComponent::class) {
|
|
||||||
attrs {
|
|
||||||
root = config
|
|
||||||
name = Name.EMPTY
|
|
||||||
this.descriptor = descriptor
|
|
||||||
this.default = default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
|
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
|
||||||
configEditor(obj.config, descriptor ?: obj.descriptor, default)
|
configEditor(obj.config, Name.EMPTY, descriptor ?: obj.descriptor, default)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package hep.dataforge.vis.editor
|
package hep.dataforge.vis.editor
|
||||||
|
|
||||||
|
import hep.dataforge.js.RFBuilder
|
||||||
import hep.dataforge.js.card
|
import hep.dataforge.js.card
|
||||||
import hep.dataforge.js.initState
|
import hep.dataforge.js.component
|
||||||
|
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
|
||||||
@ -26,15 +28,15 @@ interface TreeState : RState {
|
|||||||
var expanded: Boolean
|
var expanded: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RBuilder.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
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RBuilder.treeLabel(text: String) {
|
fun RBuilder.treeLabel(text: String) {
|
||||||
button(classes = "btn btn-link tree-label p-0") {
|
button(classes = "btn btn-link align-middle tree-label p-0") {
|
||||||
+text
|
+text
|
||||||
attrs {
|
attrs {
|
||||||
if (props.name == props.selected) {
|
if (props.name == props.selected) {
|
||||||
@ -90,7 +92,7 @@ private fun RBuilder.objectTree(props: ObjectTreeProps): Unit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ObjectTree: FunctionalComponent<ObjectTreeProps> = functionalComponent { props ->
|
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
|
||||||
objectTree(props)
|
objectTree(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package hep.dataforge.vis.editor
|
||||||
|
|
||||||
|
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
|
||||||
|
import kotlinx.html.js.onChangeFunction
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.HTMLSelectElement
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
|
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
|
||||||
|
is HTMLInputElement -> if (t.type == "checkbox") {
|
||||||
|
if (t.checked) True else False
|
||||||
|
} else {
|
||||||
|
t.value.asValue()
|
||||||
|
}
|
||||||
|
is HTMLSelectElement -> t.value.asValue()
|
||||||
|
else -> error("Unknown event target: $t")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
root.setValue(name, res)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
console.error("Can't set config property ${name} to $res")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div() {
|
||||||
|
val type = descriptor?.type?.firstOrNull()
|
||||||
|
when {
|
||||||
|
type == ValueType.BOOLEAN -> {
|
||||||
|
input(type = InputType.checkBox) {
|
||||||
|
attrs {
|
||||||
|
checked = value.boolean
|
||||||
|
onChangeFunction = onValueChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type == ValueType.NUMBER -> input(type = InputType.number, classes = "form-control w-100") {
|
||||||
|
attrs {
|
||||||
|
descriptor.attributes["step"].string?.let {
|
||||||
|
step = it
|
||||||
|
}
|
||||||
|
descriptor.attributes["min"].string?.let {
|
||||||
|
min = it
|
||||||
|
}
|
||||||
|
descriptor.attributes["max"].string?.let {
|
||||||
|
max = it
|
||||||
|
}
|
||||||
|
this.defaultValue = value.string
|
||||||
|
onChangeFunction = onValueChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor?.allowedValues?.isNotEmpty() ?: false -> select (classes = "w-100") {
|
||||||
|
descriptor!!.allowedValues.forEach {
|
||||||
|
option {
|
||||||
|
+it.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs {
|
||||||
|
multiple = false
|
||||||
|
onChangeFunction = onValueChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor?.widgetType == "color" -> input(type = InputType.color) {
|
||||||
|
attrs {
|
||||||
|
this.value = value.string
|
||||||
|
onChangeFunction = onValueChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> input(type = InputType.text, classes = "form-control w-100") {
|
||||||
|
attrs {
|
||||||
|
this.value = value.string
|
||||||
|
onChangeFunction = onValueChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package hep.dataforge.vis.editor
|
|
||||||
|
|
||||||
//val TextValueChooser = functionalComponent<ConfigEditorProps> {
|
|
||||||
//
|
|
||||||
// input(type = InputType.number, classes = "float-right") {
|
|
||||||
// attrs {
|
|
||||||
// defaultValue = value.string
|
|
||||||
// onChangeFunction = onValueChange
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -36,7 +36,7 @@ ul, .tree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tree-label-inactive {
|
.tree-label-inactive {
|
||||||
color: gray;
|
color: lightgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-label-selected{
|
.tree-label-selected{
|
||||||
|
@ -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) {
|
||||||
|
@ -79,7 +79,8 @@ class Proxy private constructor(
|
|||||||
?: error("Prototype with name $name not found in $this")
|
?: error("Prototype with name $name not found in $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
override fun allProperties(): Laminate =
|
||||||
|
Laminate(properties, mergedStyles, prototype.allProperties(), parent?.allProperties())
|
||||||
|
|
||||||
override fun attachChildren() {
|
override fun attachChildren() {
|
||||||
//do nothing
|
//do nothing
|
||||||
@ -135,7 +136,8 @@ class Proxy private constructor(
|
|||||||
//do nothing
|
//do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
override fun allProperties(): Laminate =
|
||||||
|
Laminate(properties, mergedStyles, prototype.allProperties(), parent?.allProperties())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ interface VisualObject3D : VisualObject {
|
|||||||
var rotation: Point3D?
|
var rotation: Point3D?
|
||||||
var scale: Point3D?
|
var scale: Point3D?
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor? get() = Companion.descriptor
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val VISIBLE_KEY = "visible".asName()
|
val VISIBLE_KEY = "visible".asName()
|
||||||
@ -66,12 +68,13 @@ interface VisualObject3D : VisualObject {
|
|||||||
default(true)
|
default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO replace by descriptor merge
|
||||||
|
defineValue(VisualObject.STYLE_KEY){
|
||||||
|
type(ValueType.STRING)
|
||||||
|
multiple = true
|
||||||
|
}
|
||||||
|
|
||||||
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
|
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
|
||||||
|
|
||||||
// Material3D.MATERIAL_COLOR_KEY put "#ffffff"
|
|
||||||
// Material3D.MATERIAL_OPACITY_KEY put 1.0
|
|
||||||
// Material3D.MATERIAL_WIREFRAME_KEY put false
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ abstract class MeshThreeFactory<in T : VisualObject3D>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Mesh.applyEdges(obj: VisualObject3D) {
|
fun Mesh.applyEdges(obj: VisualObject3D) {
|
||||||
children.find { it.name == "edges" }?.let {
|
children.find { it.name == "@edges" }?.let {
|
||||||
remove(it)
|
remove(it)
|
||||||
(it as LineSegments).dispose()
|
(it as LineSegments).dispose()
|
||||||
}
|
}
|
||||||
@ -93,14 +93,14 @@ fun Mesh.applyEdges(obj: VisualObject3D) {
|
|||||||
EdgesGeometry(geometry as BufferGeometry),
|
EdgesGeometry(geometry as BufferGeometry),
|
||||||
material
|
material
|
||||||
).apply {
|
).apply {
|
||||||
name = "edges"
|
name = "@edges"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
||||||
children.find { it.name == "wireframe" }?.let {
|
children.find { it.name == "@wireframe" }?.let {
|
||||||
remove(it)
|
remove(it)
|
||||||
(it as LineSegments).dispose()
|
(it as LineSegments).dispose()
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
|||||||
WireframeGeometry(geometry as BufferGeometry),
|
WireframeGeometry(geometry as BufferGeometry),
|
||||||
material
|
material
|
||||||
).apply {
|
).apply {
|
||||||
name = "wireframe"
|
name = "@wireframe"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +43,6 @@ class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
|
|||||||
canvas?.render(props.obj)
|
canvas?.render(props.obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun componentWillUnmount() {
|
|
||||||
// state.element?.clear()
|
|
||||||
// props.canvasCallback?.invoke(null)
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
|
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
|
||||||
if (prevProps.obj != props.obj) {
|
if (prevProps.obj != props.obj) {
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
|
@ -2,13 +2,14 @@ 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.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
|
||||||
import hep.dataforge.vis.VisualObject
|
import hep.dataforge.vis.VisualObject
|
||||||
import hep.dataforge.vis.editor.configEditor
|
import hep.dataforge.vis.editor.configEditor
|
||||||
import hep.dataforge.vis.editor.objectTree
|
import hep.dataforge.vis.editor.objectTree
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
|
||||||
import hep.dataforge.vis.spatial.specifications.Camera
|
import hep.dataforge.vis.spatial.specifications.Camera
|
||||||
import hep.dataforge.vis.spatial.specifications.Canvas
|
import hep.dataforge.vis.spatial.specifications.Canvas
|
||||||
import hep.dataforge.vis.spatial.three.ThreeCanvas
|
import hep.dataforge.vis.spatial.three.ThreeCanvas
|
||||||
@ -19,7 +20,7 @@ import io.ktor.client.request.get
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.RProps
|
||||||
import react.dom.*
|
import react.dom.*
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
@ -27,121 +28,112 @@ interface MMAppProps : RProps {
|
|||||||
var model: Model
|
var model: Model
|
||||||
var context: Context
|
var context: Context
|
||||||
var connection: HttpClient
|
var connection: HttpClient
|
||||||
}
|
|
||||||
|
|
||||||
interface MMAppState : RState {
|
|
||||||
var selected: Name?
|
var selected: Name?
|
||||||
var canvas: ThreeCanvas?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
|
private val canvasConfig = Canvas {
|
||||||
|
camera = Camera {
|
||||||
|
distance = 2100.0
|
||||||
|
latitude = PI / 6
|
||||||
|
azimuth = PI + PI / 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val onSelect: (Name?) -> Unit = {
|
val MMApp = component<MMAppProps> { props ->
|
||||||
setState {
|
var selected by state { props.selected }
|
||||||
selected = it
|
var canvas: ThreeCanvas? by state { null }
|
||||||
}
|
|
||||||
|
val select: (Name?) -> Unit = {
|
||||||
|
selected = it
|
||||||
}
|
}
|
||||||
|
|
||||||
private val canvasConfig = Canvas {
|
val visual = props.model.root
|
||||||
camera = Camera {
|
div("row") {
|
||||||
distance = 2100.0
|
h1("mx-auto") {
|
||||||
latitude = PI / 6
|
+"Muon monitor demo"
|
||||||
azimuth = PI + PI / 6
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div("row") {
|
||||||
override fun RBuilder.render() {
|
div("col-lg-3 mh-100 px-0") {
|
||||||
val visual = props.model.root
|
//tree
|
||||||
val selected = state.selected
|
card("Object tree") {
|
||||||
|
objectTree(visual, selected, select)
|
||||||
div("row") {
|
}
|
||||||
div("col-lg-3") {
|
}
|
||||||
//tree
|
div("col-lg-6") {
|
||||||
card("Object tree") {
|
//canvas
|
||||||
objectTree(visual, selected, onSelect)
|
child(ThreeCanvasComponent::class) {
|
||||||
|
attrs {
|
||||||
|
this.context = props.context
|
||||||
|
this.obj = visual
|
||||||
|
this.options = canvasConfig
|
||||||
|
this.selected = selected
|
||||||
|
this.clickCallback = select
|
||||||
|
this.canvasCallback = {
|
||||||
|
canvas = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("col-lg-6") {
|
}
|
||||||
//canvas
|
div("col-lg-3") {
|
||||||
child(ThreeCanvasComponent::class) {
|
div("row") {
|
||||||
attrs {
|
//settings
|
||||||
this.context = props.context
|
canvas?.let {
|
||||||
this.obj = visual
|
card("Canvas configuration") {
|
||||||
this.options = canvasConfig
|
canvasControls(it)
|
||||||
this.selected = selected
|
}
|
||||||
this.clickCallback = onSelect
|
}
|
||||||
this.canvasCallback = {
|
card("Events") {
|
||||||
setState {
|
button {
|
||||||
canvas = it
|
+"Next"
|
||||||
|
attrs {
|
||||||
|
onClickFunction = {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val event = props.connection.get<Event>("http://localhost:8080/event")
|
||||||
|
props.model.displayEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
+"Clear"
|
||||||
|
attrs {
|
||||||
|
onClickFunction = {
|
||||||
|
props.model.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("col-lg-3") {
|
div("row") {
|
||||||
div("row") {
|
div("container-fluid p-0") {
|
||||||
//settings
|
nav {
|
||||||
state.canvas?.let {
|
attrs {
|
||||||
card("Canvas configuration") {
|
attributes["aria-label"] = "breadcrumb"
|
||||||
canvasControls(it)
|
|
||||||
}
|
}
|
||||||
}
|
ol("breadcrumb") {
|
||||||
card("Events") {
|
li("breadcrumb-item") {
|
||||||
button {
|
button(classes = "btn btn-link p-0") {
|
||||||
+"Next"
|
+"World"
|
||||||
attrs {
|
attrs {
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
GlobalScope.launch {
|
selected = hep.dataforge.names.Name.EMPTY
|
||||||
val event = props.connection.get<Event>("http://localhost:8080/event")
|
|
||||||
props.model.displayEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
+"Clear"
|
|
||||||
attrs {
|
|
||||||
onClickFunction = {
|
|
||||||
props.model.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div("row") {
|
|
||||||
div("container-fluid p-0") {
|
|
||||||
nav {
|
|
||||||
attrs {
|
|
||||||
attributes["aria-label"] = "breadcrumb"
|
|
||||||
}
|
|
||||||
ol("breadcrumb") {
|
|
||||||
li("breadcrumb-item") {
|
|
||||||
button(classes = "btn btn-link p-0") {
|
|
||||||
+"World"
|
|
||||||
attrs {
|
|
||||||
onClickFunction = {
|
|
||||||
setState {
|
|
||||||
this.selected = Name.EMPTY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selected != null) {
|
}
|
||||||
val tokens = ArrayList<NameToken>(selected.length)
|
if (selected != null) {
|
||||||
selected.tokens.forEach { token ->
|
val tokens = ArrayList<NameToken>(selected?.length ?: 1)
|
||||||
tokens.add(token)
|
selected?.tokens?.forEach { token ->
|
||||||
val fullName = Name(tokens.toList())
|
tokens.add(token)
|
||||||
li("breadcrumb-item") {
|
val fullName = Name(tokens.toList())
|
||||||
button(classes = "btn btn-link p-0") {
|
li("breadcrumb-item") {
|
||||||
+token.toString()
|
button(classes = "btn btn-link p-0") {
|
||||||
attrs {
|
+token.toString()
|
||||||
onClickFunction = {
|
attrs {
|
||||||
setState {
|
onClickFunction = {
|
||||||
console.log("Selected = $fullName")
|
console.log("Selected = $fullName")
|
||||||
this.selected = fullName
|
selected = fullName
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,21 +143,23 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("row") {
|
}
|
||||||
//properties
|
div("row") {
|
||||||
if (selected != null) {
|
//properties
|
||||||
|
card("Properties") {
|
||||||
|
selected.let { selected ->
|
||||||
val selectedObject: VisualObject? = when {
|
val selectedObject: VisualObject? = when {
|
||||||
|
selected == null -> null
|
||||||
selected.isEmpty() -> visual
|
selected.isEmpty() -> visual
|
||||||
else -> visual[selected]
|
else -> visual[selected]
|
||||||
}
|
}
|
||||||
if (selectedObject != null) {
|
if (selectedObject != null) {
|
||||||
card("Properties") {
|
configEditor(selectedObject, default = selectedObject.allProperties())
|
||||||
configEditor(selectedObject, descriptor = VisualObject3D.descriptor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import io.ktor.client.HttpClient
|
|||||||
import io.ktor.client.features.json.JsonFeature
|
import io.ktor.client.features.json.JsonFeature
|
||||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import react.child
|
||||||
import react.dom.render
|
import react.dom.render
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ private class MMDemoApp : Application {
|
|||||||
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
|
||||||
|
|
||||||
render(element) {
|
render(element) {
|
||||||
child(MMAppComponent::class) {
|
child(MMApp) {
|
||||||
attrs {
|
attrs {
|
||||||
model = this@MMDemoApp.model
|
model = this@MMDemoApp.model
|
||||||
connection = this@MMDemoApp.connection
|
connection = this@MMDemoApp.connection
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user