Readme update #25 #26

Merged
pklimai merged 5 commits from readme-update into dev 2020-05-08 13:27:50 +03:00
21 changed files with 470 additions and 376 deletions
Showing only changes of commit 103fbe94ae - Show all commits

View File

@ -6,6 +6,10 @@ val dataforgeVersion: String by rootProject.extra
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
kotlin {
js {
useCommonJs()
}
sourceSets {
commonMain {
dependencies {

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

@ -1,9 +1,11 @@
package hep.dataforge.vis
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
import hep.dataforge.vis.VisualObject.Companion.STYLE_KEY
import kotlinx.serialization.Transient
@ -19,15 +21,15 @@ abstract class AbstractVisualObject : VisualObject {
protected abstract var properties: Config?
override var styles: List<String>
final override var styles: List<String>
get() = properties?.get(STYLE_KEY).stringList
set(value) {
//val allStyles = (field + value).distinct()
setProperty(STYLE_KEY, Value.of(value))
updateStyles(value)
}
protected fun updateStyles(names: List<String>) {
styleCache = null
names.mapNotNull { findStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
@ -77,7 +79,7 @@ abstract class AbstractVisualObject : VisualObject {
/**
* 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<*>? {
return if (inherit) {
@ -86,6 +88,16 @@ abstract class AbstractVisualObject : VisualObject {
properties?.get(name) ?: mergedStyles[name]
}
}
companion object {
val descriptor = NodeDescriptor {
defineValue(STYLE_KEY){
type(ValueType.STRING)
multiple = true
}
}
}
}
//fun VisualObject.findStyle(styleName: Name): Meta? {

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) {
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

View File

@ -1,10 +1,28 @@
package hep.dataforge.js
import react.RBuilder
import react.*
import kotlin.properties.ReadWriteProperty
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> {
val pair = react.useState(init)
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)

View File

@ -1,28 +1,22 @@
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.descriptors.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
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.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event
import react.RBuilder
import react.RComponent
import react.RProps
import react.*
import react.dom.*
import react.setState
interface ConfigEditorProps : RProps {
/**
* Root config object - always non null
*/
@ -42,126 +36,50 @@ interface ConfigEditorProps : RProps {
* Root descriptor
*/
var descriptor: NodeDescriptor?
}
class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
override fun TreeState.init() {
expanded = true
}
override fun componentDidMount() {
props.root.onChange(this) { name, _, _ ->
if (name == props.name) {
forceUpdate()
}
}
}
override fun componentWillUnmount() {
props.root.removeListener(this)
}
private val onClick: (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)
}
//TODO replace by separate components
private fun RBuilder.valueChooser(value: Value, descriptor: ValueDescriptor?) {
val type = descriptor?.type?.firstOrNull()
when {
type == ValueType.BOOLEAN -> {
input(type = InputType.checkBox, classes = "float-right") {
attrs {
defaultChecked = value.boolean
onChangeFunction = onValueChange
}
}
}
type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") {
attrs {
descriptor.attributes["step"].string?.let {
step = it
}
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() {
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 = item ?: defaultItem ?: descriptorItem?.defaultItem()
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val token = props.name.last()?.toString() ?: "Properties"
var kostyl by state { false }
fun update() {
kostyl = !kostyl
}
useEffectWithCleanup(listOf(props.root)) {
props.root.onChange(this) { name, _, _ ->
if (name == props.name) {
update()
}
}
return@useEffectWithCleanup { props.root.removeListener(this) }
}
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
val removeClick: (Event) -> Unit = {
props.root.remove(props.name)
update()
}
when (actualItem) {
is MetaItem.NodeItem -> {
div {
span("tree-caret") {
attrs {
if (state.expanded) {
if (expanded) {
classes += "tree-caret-down"
}
onClickFunction = onClick
onClickFunction = expanderClick
}
}
span("tree-label") {
@ -173,26 +91,19 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
}
}
}
if (state.expanded) {
if (expanded) {
ul("tree") {
val keys = buildSet<NameToken> {
item?.node?.items?.keys?.let { addAll(it) }
defaultItem?.node?.items?.keys?.let { addAll(it) }
(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") {
child(ConfigEditorComponent::class) {
attrs {
this.root = props.root
this.name = props.name + token
this.default = props.default
this.descriptor = props.descriptor
}
}
li("tree-item align-middle") {
configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
@ -200,9 +111,8 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
}
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) {
@ -210,71 +120,51 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
}
}
}
div("d-inline-flex") {
valueChooser(props.root, props.name, actualItem.value, descriptorItem as? ValueDescriptor)
}
div("col") {
valueChooser(actualItem.value, descriptorItem as? ValueDescriptor)
}
div("col-auto") {
div("dropleft p-0") {
button(classes = "btn btn-outline-primary") {
div("d-inline-flex p-1") {
button(classes = "btn btn-link") {
+"\u00D7"
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
if (item == null) {
disabled = true
} else {
onClickFunction = removeClick
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
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) {
render(this) {
child(ConfigEditorComponent::class) {
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
}
}
configEditor(config, Name.EMPTY, descriptor, default)
}
}
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)
}

View File

@ -1,7 +1,9 @@
package hep.dataforge.vis.editor
import hep.dataforge.js.RFBuilder
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.plus
import hep.dataforge.names.startsWith
@ -26,15 +28,15 @@ interface TreeState : RState {
var expanded: Boolean
}
private fun RBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by initState{ props.selected?.startsWith(props.name) ?: false }
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by state{ props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = {
expanded = !expanded
}
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
attrs {
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)
}

View File

@ -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
}
}
}
}
}

View File

@ -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
// }
// }
//}

View File

@ -36,7 +36,7 @@ ul, .tree {
}
.tree-label-inactive {
color: gray;
color: lightgrey;
}
.tree-label-selected{

View File

@ -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)

View File

@ -79,7 +79,8 @@ class Proxy private constructor(
?: 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() {
//do nothing
@ -135,7 +136,8 @@ class Proxy private constructor(
//do nothing
}
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
override fun allProperties(): Laminate =
Laminate(properties, mergedStyles, prototype.allProperties(), parent?.allProperties())
}

View File

@ -24,6 +24,8 @@ interface VisualObject3D : VisualObject {
var rotation: Point3D?
var scale: Point3D?
override val descriptor: NodeDescriptor? get() = Companion.descriptor
companion object {
val VISIBLE_KEY = "visible".asName()
@ -66,12 +68,13 @@ interface VisualObject3D : VisualObject {
default(true)
}
//TODO replace by descriptor merge
defineValue(VisualObject.STYLE_KEY){
type(ValueType.STRING)
multiple = true
}
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
}
}
}

View File

@ -80,7 +80,7 @@ abstract class MeshThreeFactory<in T : VisualObject3D>(
}
fun Mesh.applyEdges(obj: VisualObject3D) {
children.find { it.name == "edges" }?.let {
children.find { it.name == "@edges" }?.let {
remove(it)
(it as LineSegments).dispose()
}
@ -93,14 +93,14 @@ fun Mesh.applyEdges(obj: VisualObject3D) {
EdgesGeometry(geometry as BufferGeometry),
material
).apply {
name = "edges"
name = "@edges"
}
)
}
}
fun Mesh.applyWireFrame(obj: VisualObject3D) {
children.find { it.name == "wireframe" }?.let {
children.find { it.name == "@wireframe" }?.let {
remove(it)
(it as LineSegments).dispose()
}
@ -112,7 +112,7 @@ fun Mesh.applyWireFrame(obj: VisualObject3D) {
WireframeGeometry(geometry as BufferGeometry),
material
).apply {
name = "wireframe"
name = "@wireframe"
}
)
}

View File

@ -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()
}

View File

@ -43,11 +43,6 @@ class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
canvas?.render(props.obj)
}
// override fun componentWillUnmount() {
// state.element?.clear()
// props.canvasCallback?.invoke(null)
// }
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
if (prevProps.obj != props.obj) {
componentDidMount()

View File

@ -2,13 +2,14 @@ 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.state
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.editor.configEditor
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.Canvas
import hep.dataforge.vis.spatial.three.ThreeCanvas
@ -19,7 +20,7 @@ import io.ktor.client.request.get
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.html.js.onClickFunction
import react.*
import react.RProps
import react.dom.*
import kotlin.math.PI
@ -27,38 +28,36 @@ interface MMAppProps : RProps {
var model: Model
var context: Context
var connection: HttpClient
}
interface MMAppState : RState {
var selected: Name?
var canvas: ThreeCanvas?
}
class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
private val onSelect: (Name?) -> Unit = {
setState {
selected = it
}
}
private val canvasConfig = Canvas {
private val canvasConfig = Canvas {
camera = Camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
}
val MMApp = component<MMAppProps> { props ->
var selected by state { props.selected }
var canvas: ThreeCanvas? by state { null }
val select: (Name?) -> Unit = {
selected = it
}
override fun RBuilder.render() {
val visual = props.model.root
val selected = state.selected
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, onSelect)
objectTree(visual, selected, select)
}
}
div("col-lg-6") {
@ -69,19 +68,17 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
this.obj = visual
this.options = canvasConfig
this.selected = selected
this.clickCallback = onSelect
this.clickCallback = select
this.canvasCallback = {
setState {
canvas = it
}
}
}
}
}
div("col-lg-3") {
div("row") {
//settings
state.canvas?.let {
canvas?.let {
card("Canvas configuration") {
canvasControls(it)
}
@ -120,16 +117,14 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
+"World"
attrs {
onClickFunction = {
setState {
this.selected = Name.EMPTY
}
selected = hep.dataforge.names.Name.EMPTY
}
}
}
}
if (selected != null) {
val tokens = ArrayList<NameToken>(selected.length)
selected.tokens.forEach { token ->
val tokens = ArrayList<NameToken>(selected?.length ?: 1)
selected?.tokens?.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
li("breadcrumb-item") {
@ -137,10 +132,8 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
+token.toString()
attrs {
onClickFunction = {
setState {
console.log("Selected = $fullName")
this.selected = fullName
}
selected = fullName
}
}
}
@ -153,19 +146,20 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
}
div("row") {
//properties
if (selected != null) {
card("Properties") {
selected.let { selected ->
val selectedObject: VisualObject? = when {
selected == null -> null
selected.isEmpty() -> visual
else -> visual[selected]
}
if (selectedObject != null) {
card("Properties") {
configEditor(selectedObject, descriptor = VisualObject3D.descriptor)
}
configEditor(selectedObject, default = selectedObject.allProperties())
}
}
}
}
}
}
}

View File

@ -8,6 +8,7 @@ import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.serialization.json.Json
import react.child
import react.dom.render
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")
render(element) {
child(MMAppComponent::class) {
child(MMApp) {
attrs {
model = this@MMDemoApp.model
connection = this@MMDemoApp.connection

View File

@ -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>