Type-safe react hooks

This commit is contained in:
Alexander Nozik 2020-04-20 17:52:22 +03:00
parent 63f2713875
commit 2d79e8e422
9 changed files with 96 additions and 20 deletions

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

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

@ -1,11 +1,28 @@
package hep.dataforge.js
import react.RBuilder
import react.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class RFBuilder : RBuilder()
fun <T> RBuilder.initState(init: () -> T): ReadWriteProperty<Any?, T> =
/**
* 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.initState(init: () -> T): ReadWriteProperty<Any?, T> =
object : ReadWriteProperty<Any?, T> {
val pair = react.useState(init)
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
@ -17,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,6 +1,9 @@
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.meta.*
import hep.dataforge.meta.descriptors.*
import hep.dataforge.names.Name
@ -37,11 +40,12 @@ interface ConfigEditorProps : RProps {
var descriptor: NodeDescriptor?
}
private fun RBuilder.configEditorItem(props: ConfigEditorProps) {
private fun RFBuilder.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)
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) }
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val token = props.name.last()?.toString() ?: "Properties"
@ -60,8 +64,6 @@ private fun RBuilder.configEditorItem(props: ConfigEditorProps) {
return@useEffectWithCleanup { props.root.removeListener(this) }
}
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
@ -162,7 +164,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorProps) {
}
}
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = functionalComponent { configEditorItem(it) }
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = component { configEditorItem(it) }
fun RBuilder.configEditor(
config: Config,

View File

@ -1,6 +1,8 @@
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.names.Name
import hep.dataforge.names.plus
@ -26,7 +28,7 @@ interface TreeState : RState {
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 }
val onClick: (Event) -> Unit = {
@ -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

@ -1,5 +1,6 @@
package hep.dataforge.vis.editor
import hep.dataforge.js.component
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get
import hep.dataforge.meta.string
@ -11,11 +12,11 @@ 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
import react.functionalComponent
interface ValueChooserProps : RProps {
var value: Value
@ -23,7 +24,35 @@ interface ValueChooserProps : RProps {
var valueChanged: (Value?) -> Unit
}
val ValueChooser = functionalComponent<ValueChooserProps> { props ->
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

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

@ -70,6 +70,12 @@ interface VisualObject3D : VisualObject {
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

View File

@ -2,6 +2,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.names.Name
import hep.dataforge.names.NameToken
@ -21,7 +22,6 @@ import kotlinx.coroutines.launch
import kotlinx.html.js.onClickFunction
import react.RProps
import react.dom.*
import react.functionalComponent
import kotlin.math.PI
interface MMAppProps : RProps {
@ -39,7 +39,7 @@ private val canvasConfig = Canvas {
}
}
val MMApp = functionalComponent<MMAppProps> { props ->
val MMApp = component<MMAppProps> { props ->
var selected by initState { props.selected }
var canvas: ThreeCanvas? by initState { null }