Fix react properties update bug
This commit is contained in:
parent
38db0e30ed
commit
e0346b7db5
@ -4,6 +4,7 @@ import react.RBuilder
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
||||
fun <T> RBuilder.initState(init: () -> T): ReadWriteProperty<Any?, T> =
|
||||
object : ReadWriteProperty<Any?, T> {
|
||||
val pair = react.useState(init)
|
||||
|
@ -1,28 +1,21 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.js.initState
|
||||
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 hep.dataforge.values.Value
|
||||
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 +35,65 @@ interface ConfigEditorProps : RProps {
|
||||
* Root descriptor
|
||||
*/
|
||||
var descriptor: NodeDescriptor?
|
||||
|
||||
}
|
||||
|
||||
class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
||||
private fun RBuilder.configEditorItem(props: ConfigEditorProps) {
|
||||
var expanded: Boolean by initState { true }
|
||||
val item = props.root[props.name]
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val defaultItem = props.default?.get(props.name)
|
||||
|
||||
override fun TreeState.init() {
|
||||
expanded = true
|
||||
val token = props.name.last()?.toString() ?: "Properties"
|
||||
|
||||
var kostyl by initState { false }
|
||||
|
||||
fun update() {
|
||||
kostyl = !kostyl
|
||||
}
|
||||
|
||||
override fun componentDidMount() {
|
||||
useEffectWithCleanup(listOf(props.root)) {
|
||||
props.root.onChange(this) { name, _, _ ->
|
||||
if (name == props.name) {
|
||||
forceUpdate()
|
||||
update()
|
||||
}
|
||||
}
|
||||
return@useEffectWithCleanup { props.root.removeListener(this) }
|
||||
}
|
||||
|
||||
override fun componentWillUnmount() {
|
||||
props.root.removeListener(this)
|
||||
}
|
||||
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
||||
|
||||
private val onClick: (Event) -> Unit = {
|
||||
setState {
|
||||
val expanderClick: (Event) -> Unit = {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
val removeClick: (Event) -> Unit = {
|
||||
props.root.remove(props.name)
|
||||
update()
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
if (expanded) {
|
||||
classes += "tree-caret-down"
|
||||
}
|
||||
onClickFunction = onClick
|
||||
onClickFunction = expanderClick
|
||||
}
|
||||
}
|
||||
span("tree-label") {
|
||||
@ -173,26 +105,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,30 +137,25 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
||||
}
|
||||
}
|
||||
div("col") {
|
||||
valueChooser(actualItem.value, descriptorItem as? ValueDescriptor)
|
||||
}
|
||||
div("col-auto") {
|
||||
div("dropleft p-0") {
|
||||
button(classes = "btn btn-outline-primary") {
|
||||
console.log("1: Setting ${props.name} to ${actualItem.value}")
|
||||
val value = actualItem.value
|
||||
child(ValueChooser) {
|
||||
attrs {
|
||||
type = ButtonType.button
|
||||
attributes["data-toggle"] = "dropdown"
|
||||
attributes["aria-haspopup"] = "true"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["data-boundary"] = "viewport"
|
||||
console.log("2: Setting ${props.name} to $value")
|
||||
this.value = value
|
||||
this.descriptor = descriptorItem as? ValueDescriptor
|
||||
this.valueChanged = valueChanged
|
||||
}
|
||||
+"\u22ee"
|
||||
}
|
||||
div(classes = "dropdown-menu") {
|
||||
button(classes = "btn btn-outline dropdown-item") {
|
||||
+"Info"
|
||||
}
|
||||
if (item != null) {
|
||||
button(classes = "btn btn-outline dropdown-item") {
|
||||
+"""Clear"""
|
||||
}
|
||||
button(classes = "btn btn-link") {
|
||||
+"\u00D7"
|
||||
attrs {
|
||||
onClickFunction = removeValue
|
||||
if (item == null) {
|
||||
disabled = true
|
||||
} else {
|
||||
onClickFunction = removeClick
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,38 +163,32 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = functionalComponent { 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)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ private fun RBuilder.objectTree(props: ObjectTreeProps): Unit {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -0,0 +1,99 @@
|
||||
package hep.dataforge.vis.editor
|
||||
|
||||
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
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.RProps
|
||||
import react.dom.div
|
||||
import react.dom.input
|
||||
import react.dom.option
|
||||
import react.dom.select
|
||||
import react.functionalComponent
|
||||
|
||||
interface ValueChooserProps : RProps {
|
||||
var value: Value
|
||||
var descriptor: ValueDescriptor?
|
||||
var valueChanged: (Value?) -> Unit
|
||||
}
|
||||
|
||||
val ValueChooser = functionalComponent<ValueChooserProps> { props ->
|
||||
// var state by initState {props.value }
|
||||
val descriptor = props.descriptor
|
||||
|
||||
console.log("3: Set ${props.value}")
|
||||
|
||||
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")
|
||||
}
|
||||
// state = res
|
||||
props.valueChanged(res)
|
||||
}
|
||||
|
||||
div {
|
||||
val type = descriptor?.type?.firstOrNull()
|
||||
when {
|
||||
type == ValueType.BOOLEAN -> {
|
||||
input(type = InputType.checkBox, classes = "float-right") {
|
||||
attrs {
|
||||
checked = props.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
|
||||
}
|
||||
this.value = props.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 {
|
||||
this.value = props.value.string
|
||||
onChangeFunction = onValueChange
|
||||
}
|
||||
}
|
||||
else -> input(type = InputType.text, classes = "float-right") {
|
||||
attrs {
|
||||
this.value = props.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 {
|
||||
color: gray;
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
.tree-label-selected{
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -2,13 +2,13 @@ package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.js.card
|
||||
import hep.dataforge.js.initState
|
||||
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,46 +19,41 @@ 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 react.functionalComponent
|
||||
import kotlin.math.PI
|
||||
|
||||
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 = functionalComponent<MMAppProps> { props ->
|
||||
var selected by initState { props.selected }
|
||||
var canvas: ThreeCanvas? by initState { 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") {
|
||||
//tree
|
||||
card("Object tree") {
|
||||
objectTree(visual, selected, onSelect)
|
||||
objectTree(visual, selected, select)
|
||||
}
|
||||
}
|
||||
div("col-lg-6") {
|
||||
@ -69,19 +64,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 +113,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 +128,8 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
|
||||
+token.toString()
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
setState {
|
||||
console.log("Selected = $fullName")
|
||||
this.selected = fullName
|
||||
}
|
||||
selected = fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,15 +142,15 @@ 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user