Refactor react ThreeCanvas to functional component

This commit is contained in:
Alexander Nozik 2020-10-30 10:02:49 +03:00
parent 3edb00b6bf
commit aaad836567
14 changed files with 162 additions and 149 deletions

View File

@ -7,7 +7,6 @@ import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.bootstrap.*
import hep.dataforge.vision.gdml.toVision
import hep.dataforge.vision.react.component
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.SolidGroup
@ -22,11 +21,8 @@ import kscience.gdml.GDML
import kscience.gdml.decodeFromString
import org.w3c.files.FileReader
import org.w3c.files.get
import react.RProps
import react.*
import react.dom.h1
import react.getValue
import react.setValue
import react.useState
import styled.css
external interface GDMLAppProps : RProps {
@ -44,7 +40,7 @@ external interface GDMLAppProps : RProps {
//}
@JsExport
val GDMLApp = component<GDMLAppProps> { props ->
val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
var selected by useState { props.selected }
var canvas: ThreeCanvas? by useState { null }
var vision: Vision? by useState { props.rootObject }
@ -113,16 +109,14 @@ val GDMLApp = component<GDMLAppProps> { props ->
+"order-xl-2"
}
//canvas
(vision as? Solid)?.let { visual3D ->
child(ThreeCanvasComponent::class) {
attrs {
this.context = props.context
this.obj = visual3D
this.selected = selected
this.clickCallback = select
this.canvasCallback = {
canvas = it
}
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = vision as? Solid
this.selected = selected
this.clickCallback = select
this.canvasCallback = {
canvas = it
}
}
}

View File

@ -8,7 +8,6 @@ import hep.dataforge.names.length
import hep.dataforge.vision.Vision
import hep.dataforge.vision.bootstrap.canvasControls
import hep.dataforge.vision.bootstrap.card
import hep.dataforge.vision.react.component
import hep.dataforge.vision.react.configEditor
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.specifications.Camera
@ -20,11 +19,10 @@ import io.ktor.client.request.get
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.html.js.onClickFunction
import react.RProps
import react.*
import react.dom.*
import react.getValue
import react.setValue
import react.useState
import styled.css
import styled.styledDiv
import kotlin.math.PI
external interface MMAppProps : RProps {
@ -43,8 +41,8 @@ private val canvasConfig = Canvas3DOptions {
}
@JsExport
val MMApp = component<MMAppProps> { props ->
var selected by useState { props.selected }
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
var selected by useState { props.selected }
var canvas: ThreeCanvas? by useState { null }
val select: (Name?) -> Unit = {
@ -59,7 +57,12 @@ val MMApp = component<MMAppProps> { props ->
}
}
div("row") {
div("col-lg-3 px-0 overflow-auto") {
styledDiv {
css {
+"col-lg-3"
+"px-0"
+"overflow-auto"
}
//tree
card("Object tree") {
objectTree(root, selected, select)
@ -67,7 +70,7 @@ val MMApp = component<MMAppProps> { props ->
}
div("col-lg-6") {
//canvas
child(ThreeCanvasComponent::class) {
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = root

View File

@ -3,16 +3,25 @@ import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.vision.bootstrap.visionPropertyEditor
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.Point3D
import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.box
import hep.dataforge.vision.solid.group
import hep.dataforge.vision.solid.*
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
import hep.dataforge.vision.solid.three.ThreePlugin
import hep.dataforge.vision.solid.three.threeCanvas
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
import react.RBuilder
import react.child
import react.dom.div
import react.dom.render
import kotlin.browser.document
public fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
child(ThreeCanvasComponent) {
attrs {
this.obj = object3D
this.options = Canvas3DOptions.invoke(options)
}
}
}
private class PlayGroundApp : Application {

View File

@ -19,29 +19,29 @@ public external interface ConfigEditorItemProps : RProps {
/**
* Root config object - always non null
*/
var root: Config
public var root: Config
/**
* Full path to the displayed node in [root]. Could be empty
*/
var name: Name
public var name: Name
/**
* Root default
*/
var default: Meta?
public var default: Meta?
/**
* Root descriptor
*/
var descriptor: NodeDescriptor?
public var descriptor: NodeDescriptor?
}
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> = component { props ->
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> = functionalComponent("ConfigEditorItem") { props ->
configEditorItem(props)
}
private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
var expanded: Boolean by useState { true }
var item: MetaItem<Config>? by useState { props.root[props.name] }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
@ -198,7 +198,7 @@ public external interface ConfigEditorProps : RProps {
}
@JsExport
public val ConfigEditor: FunctionalComponent<ConfigEditorProps> = component { props ->
public val ConfigEditor: FunctionalComponent<ConfigEditorProps> = functionalComponent("ConfigEditor") { props ->
child(ConfigEditorItem) {
attrs {
this.key = ""
@ -223,7 +223,7 @@ public fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = nu
}
}
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
public fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
child(ConfigEditor) {
attrs {
this.key = key?.toString() ?: ""
@ -234,7 +234,7 @@ fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, de
}
}
fun RBuilder.configEditor(
public fun RBuilder.configEditor(
obj: Configurable,
descriptor: NodeDescriptor? = obj.descriptor,
default: Meta? = null,

View File

@ -21,24 +21,24 @@ public external interface MetaViewerProps : RProps {
/**
* Root meta
*/
var root: Meta
public var root: Meta
/**
* Full path to the displayed node in [root]. Could be empty
*/
var name: Name
public var name: Name
/**
* Root descriptor
*/
var descriptor: NodeDescriptor?
public var descriptor: NodeDescriptor?
}
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = component { props ->
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
metaViewerItem(props)
}
private fun RFBuilder.metaViewerItem(props: MetaViewerProps) {
private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
var expanded: Boolean by useState { true }
val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
@ -137,7 +137,7 @@ private fun RFBuilder.metaViewerItem(props: MetaViewerProps) {
}
@JsExport
val MetaViewer = component<MetaViewerProps> { props ->
public val MetaViewer:FunctionalComponent<MetaViewerProps> = functionalComponent<MetaViewerProps>("MetaViewer") { props ->
child(MetaViewerItem) {
attrs {
this.key = ""
@ -148,7 +148,7 @@ val MetaViewer = component<MetaViewerProps> { props ->
}
}
fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) {
public fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) {
child(MetaViewer) {
attrs {
this.key = key?.toString() ?: ""

View File

@ -19,7 +19,7 @@ public external interface ObjectTreeProps : RProps {
public var clickCallback: (Name) -> Unit
}
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
private fun RBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by useState { props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = {
@ -103,11 +103,11 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
}
@JsExport
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
public val ObjectTree: FunctionalComponent<ObjectTreeProps> = functionalComponent("ObjectTree") { props ->
objectTree(props)
}
fun RBuilder.objectTree(
public fun RBuilder.objectTree(
vision: Vision,
selected: Name? = null,
clickCallback: (Name) -> Unit = {}

View File

@ -4,11 +4,11 @@ import kotlinx.css.*
import kotlinx.css.properties.*
import styled.StyleSheet
object TreeStyles : StyleSheet("treeStyles", true) {
public object TreeStyles : StyleSheet("treeStyles", true) {
/**
* Remove default bullets
*/
val tree by css {
public val tree by css {
paddingLeft = 8.px
marginLeft = 0.px
listStyleType = ListStyleType.none
@ -17,7 +17,7 @@ object TreeStyles : StyleSheet("treeStyles", true) {
/**
* Style the caret/arrow
*/
val treeCaret by css {
public val treeCaret by css {
cursor = Cursor.pointer
userSelect = UserSelect.none
/* Create the caret/arrow with a unicode, and style it */

View File

@ -6,11 +6,12 @@ import kotlinx.css.display
import kotlinx.css.flexDirection
import kotlinx.html.DIV
import react.RBuilder
import react.ReactElement
import styled.StyledDOMBuilder
import styled.css
import styled.styledDiv
inline fun RBuilder.flexColumn(block: StyledDOMBuilder<DIV>.() -> Unit) =
public inline fun RBuilder.flexColumn(block: StyledDOMBuilder<DIV>.() -> Unit): ReactElement =
styledDiv {
css {
display = Display.flex
@ -19,7 +20,7 @@ inline fun RBuilder.flexColumn(block: StyledDOMBuilder<DIV>.() -> Unit) =
block()
}
inline fun RBuilder.flexRow(block: StyledDOMBuilder<DIV>.() -> Unit) =
public inline fun RBuilder.flexRow(block: StyledDOMBuilder<DIV>.() -> Unit): ReactElement =
styledDiv {
css {
display = Display.flex

View File

@ -1,24 +0,0 @@
package hep.dataforge.vision.react
import react.*
public class RFBuilder : RBuilder()
/**
* Get functional component from [func]
*/
public inline fun <P : RProps> component(
crossinline 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())
}
}
}
//
//public fun <T> RFBuilder.memoize(vararg deps: dynamic, builder: () -> T): T = useMemo(builder, deps)

View File

@ -13,9 +13,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import mu.KotlinLogging
expect class Counter() {
fun get(): Int
fun incrementAndGet(): Int
public expect class Counter() {
public fun get(): Int
public fun incrementAndGet(): Int
}
private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && other == null) {

View File

@ -1,8 +1,8 @@
package hep.dataforge.vision.gdml
actual class Counter {
public actual class Counter {
private var count: Int = 0
actual fun get(): Int = count
public actual fun get(): Int = count
actual fun incrementAndGet(): Int = count++
public actual fun incrementAndGet(): Int = count++
}

View File

@ -7,7 +7,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
actual typealias Counter = AtomicInteger
public actual typealias Counter = AtomicInteger
fun GDML.Companion.readFile(file: Path): GDML {
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")

View File

@ -29,11 +29,11 @@ import info.laht.threekt.math.Vector2
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import info.laht.threekt.scenes.Scene
import kotlinx.browser.window
import kotlinx.dom.clear
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.events.MouseEvent
import kotlin.browser.window
import kotlin.dom.clear
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
@ -41,11 +41,16 @@ import kotlin.math.sin
/**
*
*/
class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Canvas3DOptions) : Renderer<Solid> {
public class ThreeCanvas(
element: HTMLElement,
public val three: ThreePlugin,
public val options: Canvas3DOptions,
public val onClick: ((Name?) -> Unit)? = null,
) : Renderer<Solid> {
override val context: Context get() = three.context
var content: Solid? = null
public var content: Solid? = null
private set
private var root: Object3D? = null
@ -53,17 +58,15 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Can
private val raycaster = Raycaster()
private val mousePosition: Vector2 = Vector2()
var onClick: ((Name?) -> Unit)? = null
val axes = AxesHelper(options.axes.size.toInt()).apply {
public val axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply {
visible = options.axes.visible
}
val scene: Scene = Scene().apply {
public val scene: Scene = Scene().apply {
add(axes)
}
val camera = buildCamera(options.camera)
public val camera: PerspectiveCamera = buildCamera(options.camera)
private var picked: Object3D? = null
@ -166,7 +169,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Can
}
}
fun clear() {
public fun clear() {
scene.children.find { it.name == "@root" }?.let {
scene.remove(it)
}
@ -192,7 +195,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Can
private fun Object3D.toggleHighlight(
highlight: Boolean,
edgesName: String,
material: LineBasicMaterial = SELECTED_MATERIAL
material: LineBasicMaterial = SELECTED_MATERIAL,
) {
if (userData[DO_NOT_HIGHLIGHT_TAG] == true) {
return
@ -220,7 +223,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Can
/**
* Toggle highlight for element with given name
*/
fun select(name: Name?) {
public fun select(name: Name?) {
if (name == null) {
selected?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
selected = null
@ -234,15 +237,21 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val options: Can
}
}
companion object {
const val DO_NOT_HIGHLIGHT_TAG = "doNotHighlight"
public companion object {
public const val DO_NOT_HIGHLIGHT_TAG = "doNotHighlight"
private const val HIGHLIGHT_NAME = "@highlight"
private const val SELECT_NAME = "@select"
}
}
fun ThreePlugin.output(element: HTMLElement, spec: Canvas3DOptions = Canvas3DOptions.empty()): ThreeCanvas =
ThreeCanvas(element, this, spec)
public fun ThreePlugin.output(
element: HTMLElement,
spec: Canvas3DOptions = Canvas3DOptions.empty(),
onClick: ((Name?) -> Unit)? = null,
): ThreeCanvas = ThreeCanvas(element, this, spec, onClick)
fun ThreePlugin.render(element: HTMLElement, obj: Solid, spec: Canvas3DOptions = Canvas3DOptions.empty()): Unit =
output(element, spec).render(obj)
public fun ThreePlugin.render(
element: HTMLElement, obj: Solid,
spec: Canvas3DOptions = Canvas3DOptions.empty(),
onClick: ((Name?) -> Unit)? = null,
): Unit = output(element, spec, onClick).render(obj)

View File

@ -6,67 +6,88 @@ import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import react.RBuilder
import react.RComponent
import react.RProps
import react.RState
import react.*
import react.dom.div
import react.dom.findDOMNode
public external interface ThreeCanvasProps : RProps {
var context: Context
var obj: Solid
var options: Canvas3DOptions?
var selected: Name?
var clickCallback: (Name?) -> Unit
var canvasCallback: ((ThreeCanvas?) -> Unit)?
public var context: Context
public var obj: Solid?
public var options: Canvas3DOptions?
public var selected: Name?
public var clickCallback: (Name?) -> Unit
public var canvasCallback: ((ThreeCanvas?) -> Unit)?
}
public external interface ThreeCanvasState : RState {
var element: Element?
public var element: Element?
// var canvas: ThreeCanvas?
}
@JsExport
public class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functionalComponent(
"ThreeCanvasComponent"
) { props ->
val elementRef = useRef<Element?>(null)
var canvas by useState<ThreeCanvas?>(null)
private var canvas: ThreeCanvas? = null
override fun componentDidMount() {
if(canvas == null) {
val element = state.element as? HTMLElement ?: error("Canvas element not found")
useEffect(listOf(props.context, props.obj, props.options, elementRef)) {
if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
canvas = three.output(element, props.options ?: Canvas3DOptions.empty()).apply {
onClick = props.clickCallback
}
props.canvasCallback?.invoke(canvas)
}
canvas?.render(props.obj)
}
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
if (prevProps.obj != props.obj) {
componentDidMount()
}
if (prevProps.selected != props.selected) {
canvas?.select(props.selected)
val newCanvas = three.output(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
props.canvasCallback?.invoke(newCanvas)
canvas = newCanvas
}
}
override fun RBuilder.render() {
div {
ref {
state.element = findDOMNode(it)
useEffect(listOf(canvas, props.obj)) {
props.obj?.let { obj ->
if (canvas?.content != obj) {
canvas?.render(obj)
}
}
}
useEffect(listOf(canvas, props.selected)) {
canvas?.select(props.selected)
}
div {
ref = elementRef
}
}
public fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
child(ThreeCanvasComponent::class) {
attrs {
this.obj = object3D
this.options = Canvas3DOptions.invoke(options)
}
}
}
//public class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
//
// private var canvas: ThreeCanvas? = null
//
// override fun componentDidMount() {
// props.obj?.let { obj ->
// if (canvas == null) {
// val element = state.element as? HTMLElement ?: error("Canvas element not found")
// val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
// canvas = three.output(element, props.options ?: Canvas3DOptions.empty()).apply {
// onClick = props.clickCallback
// }
// props.canvasCallback?.invoke(canvas)
// }
// canvas?.render(obj)
// }
// }
//
// override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
// if (prevProps.obj != props.obj) {
// componentDidMount()
// }
// if (prevProps.selected != props.selected) {
// canvas?.select(props.selected)
// }
// }
//
// override fun RBuilder.render() {
// div {
// ref {
// state.element = findDOMNode(it)
// }
// }
// }
//}