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") //val kvisionVersion: String by rootProject.extra("2.0.0-M1")
kotlin { kotlin {
js {
useCommonJs()
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {

View File

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

View File

@ -1,11 +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
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> { 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 {
@ -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 package hep.dataforge.vis.editor
import hep.dataforge.js.RFBuilder
import hep.dataforge.js.component
import hep.dataforge.js.initState import hep.dataforge.js.initState
import hep.dataforge.js.memoize
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
@ -37,11 +40,12 @@ interface ConfigEditorProps : RProps {
var descriptor: NodeDescriptor? var descriptor: NodeDescriptor?
} }
private fun RBuilder.configEditorItem(props: ConfigEditorProps) { private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
var expanded: Boolean by initState { true } var expanded: Boolean by initState { true }
val item = props.root[props.name] val item = memoize(props.root, props.name) { props.root[props.name] }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val descriptorItem: ItemDescriptor? = memoize(props.descriptor, props.name) { props.descriptor?.get(props.name) }
val defaultItem = props.default?.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" val token = props.name.last()?.toString() ?: "Properties"
@ -60,8 +64,6 @@ private fun RBuilder.configEditorItem(props: ConfigEditorProps) {
return@useEffectWithCleanup { props.root.removeListener(this) } return@useEffectWithCleanup { props.root.removeListener(this) }
} }
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val expanderClick: (Event) -> Unit = { val expanderClick: (Event) -> Unit = {
expanded = !expanded 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( fun RBuilder.configEditor(
config: Config, config: Config,

View File

@ -1,6 +1,8 @@
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.component
import hep.dataforge.js.initState import hep.dataforge.js.initState
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
@ -26,7 +28,7 @@ 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 initState{ props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = { 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) objectTree(props)
} }

View File

@ -1,5 +1,6 @@
package hep.dataforge.vis.editor package hep.dataforge.vis.editor
import hep.dataforge.js.component
import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.string import hep.dataforge.meta.string
@ -11,11 +12,11 @@ import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.RProps import react.RProps
import react.RState
import react.dom.div import react.dom.div
import react.dom.input import react.dom.input
import react.dom.option import react.dom.option
import react.dom.select import react.dom.select
import react.functionalComponent
interface ValueChooserProps : RProps { interface ValueChooserProps : RProps {
var value: Value var value: Value
@ -23,7 +24,35 @@ interface ValueChooserProps : RProps {
var valueChanged: (Value?) -> Unit 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 } // var state by initState {props.value }
val descriptor = props.descriptor val descriptor = props.descriptor

View File

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

View File

@ -70,6 +70,12 @@ interface VisualObject3D : VisualObject {
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor) 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_COLOR_KEY put "#ffffff"
// Material3D.MATERIAL_OPACITY_KEY put 1.0 // Material3D.MATERIAL_OPACITY_KEY put 1.0
// Material3D.MATERIAL_WIREFRAME_KEY put false // 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.context.Context
import hep.dataforge.js.card import hep.dataforge.js.card
import hep.dataforge.js.component
import hep.dataforge.js.initState import hep.dataforge.js.initState
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
@ -21,7 +22,6 @@ import kotlinx.coroutines.launch
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import react.RProps import react.RProps
import react.dom.* import react.dom.*
import react.functionalComponent
import kotlin.math.PI import kotlin.math.PI
interface MMAppProps : RProps { 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 selected by initState { props.selected }
var canvas: ThreeCanvas? by initState { null } var canvas: ThreeCanvas? by initState { null }