Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Peter Klimai 2020-04-29 00:29:17 +03:00
commit 103fbe94ae
21 changed files with 470 additions and 376 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

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

@ -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) { inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) {
div("card w-100") { div("card w-100 h-100") {
div("card-body") { div("card-body") {
h3(classes = "card-title") { +title } h3(classes = "card-title") {
+title
}
block() block()
} }
} }
@ -78,7 +80,7 @@ fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.
elements.forEachIndexed { index, (title, builder) -> elements.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading" val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse" val collapseID = "${id}-${index}-collapse"
div("card") { div("card p-0 m-0") {
div("card-header") { div("card-header") {
attrs { attrs {
this.id = headerID this.id = headerID

View File

@ -1,10 +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
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> { 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 {
@ -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 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.*
import hep.dataforge.meta.descriptors.* import hep.dataforge.meta.descriptors.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.plus 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.classes
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.RBuilder import react.*
import react.RComponent
import react.RProps
import react.dom.* import react.dom.*
import react.setState
interface ConfigEditorProps : RProps { interface ConfigEditorProps : RProps {
/** /**
* Root config object - always non null * Root config object - always non null
*/ */
@ -42,126 +36,50 @@ interface ConfigEditorProps : RProps {
* Root descriptor * Root descriptor
*/ */
var descriptor: NodeDescriptor? var descriptor: NodeDescriptor?
} }
class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() { private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
var expanded: Boolean by state { true }
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() {
val item = props.root[props.name] val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val defaultItem = props.default?.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" 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) { when (actualItem) {
is MetaItem.NodeItem -> { is MetaItem.NodeItem -> {
div { div {
span("tree-caret") { span("tree-caret") {
attrs { attrs {
if (state.expanded) { if (expanded) {
classes += "tree-caret-down" classes += "tree-caret-down"
} }
onClickFunction = onClick onClickFunction = expanderClick
} }
} }
span("tree-label") { span("tree-label") {
@ -173,26 +91,19 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
} }
} }
} }
if (state.expanded) { if (expanded) {
ul("tree") { ul("tree") {
val keys = buildSet<NameToken> { val keys = buildSet<NameToken> {
item?.node?.items?.keys?.let { addAll(it) }
defaultItem?.node?.items?.keys?.let { addAll(it) }
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach { (descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
add(NameToken(it)) add(NameToken(it))
} }
item?.node?.items?.keys?.let { addAll(it) }
defaultItem?.node?.items?.keys?.let { addAll(it) }
} }
keys.forEach { token -> keys.forEach { token ->
li("tree-item") { li("tree-item align-middle") {
child(ConfigEditorComponent::class) { configEditor(props.root, props.name + token, props.descriptor, props.default)
attrs {
this.root = props.root
this.name = props.name + token
this.default = props.default
this.descriptor = props.descriptor
}
}
} }
} }
} }
@ -200,9 +111,8 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
} }
is MetaItem.ValueItem -> { is MetaItem.ValueItem -> {
div { div {
div("row") { div("d-flex flex-row align-items-center") {
div("col") { div("flex-grow-1 p-1 mr-auto tree-label") {
p("tree-label") {
+token +token
attrs { attrs {
if (item == null) { 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") { div("d-inline-flex p-1") {
valueChooser(actualItem.value, descriptorItem as? ValueDescriptor) button(classes = "btn btn-link") {
} +"\u00D7"
div("col-auto") {
div("dropleft p-0") {
button(classes = "btn btn-outline-primary") {
attrs { attrs {
type = ButtonType.button if (item == null) {
attributes["data-toggle"] = "dropdown" disabled = true
attributes["aria-haspopup"] = "true" } else {
attributes["aria-expanded"] = "false" onClickFunction = removeClick
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
} }
} }
}
}
}
}
}
}
}
} 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) { fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
render(this) { render(this) {
child(ConfigEditorComponent::class) { configEditor(config, Name.EMPTY, descriptor, default)
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
}
}
} }
} }
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) { 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 package hep.dataforge.vis.editor
import hep.dataforge.js.RFBuilder
import hep.dataforge.js.card 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.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
@ -26,15 +28,15 @@ 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 state{ props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = { val onClick: (Event) -> Unit = {
expanded = !expanded expanded = !expanded
} }
fun RBuilder.treeLabel(text: String) { 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 +text
attrs { attrs {
if (props.name == props.selected) { 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) 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 { .tree-label-inactive {
color: gray; color: lightgrey;
} }
.tree-label-selected{ .tree-label-selected{

View File

@ -56,14 +56,12 @@ class Material3D : Scheme() {
defineValue(OPACITY_KEY) { defineValue(OPACITY_KEY) {
type(ValueType.NUMBER) type(ValueType.NUMBER)
default(1.0) default(1.0)
configure { config["attributes"] = Meta {
"attributes" to {
this["min"] = 0.0 this["min"] = 0.0
this["max"] = 1.0 this["max"] = 1.0
this["step"] = 0.1 this["step"] = 0.1
} }
} }
}
defineValue(WIREFRAME_KEY) { defineValue(WIREFRAME_KEY) {
type(ValueType.BOOLEAN) type(ValueType.BOOLEAN)
default(false) default(false)

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

@ -24,6 +24,8 @@ interface VisualObject3D : VisualObject {
var rotation: Point3D? var rotation: Point3D?
var scale: Point3D? var scale: Point3D?
override val descriptor: NodeDescriptor? get() = Companion.descriptor
companion object { companion object {
val VISIBLE_KEY = "visible".asName() val VISIBLE_KEY = "visible".asName()
@ -66,12 +68,13 @@ interface VisualObject3D : VisualObject {
default(true) default(true)
} }
//TODO replace by descriptor merge
defineValue(VisualObject.STYLE_KEY){
type(ValueType.STRING)
multiple = true
}
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor) 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) { fun Mesh.applyEdges(obj: VisualObject3D) {
children.find { it.name == "edges" }?.let { children.find { it.name == "@edges" }?.let {
remove(it) remove(it)
(it as LineSegments).dispose() (it as LineSegments).dispose()
} }
@ -93,14 +93,14 @@ fun Mesh.applyEdges(obj: VisualObject3D) {
EdgesGeometry(geometry as BufferGeometry), EdgesGeometry(geometry as BufferGeometry),
material material
).apply { ).apply {
name = "edges" name = "@edges"
} }
) )
} }
} }
fun Mesh.applyWireFrame(obj: VisualObject3D) { fun Mesh.applyWireFrame(obj: VisualObject3D) {
children.find { it.name == "wireframe" }?.let { children.find { it.name == "@wireframe" }?.let {
remove(it) remove(it)
(it as LineSegments).dispose() (it as LineSegments).dispose()
} }
@ -112,7 +112,7 @@ fun Mesh.applyWireFrame(obj: VisualObject3D) {
WireframeGeometry(geometry as BufferGeometry), WireframeGeometry(geometry as BufferGeometry),
material material
).apply { ).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) 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 = { window.onresize = {
renderer.setSize(element.offsetWidth, element.offsetWidth) renderer.setSize(element.clientWidth, element.clientWidth)
camera.updateProjectionMatrix() camera.updateProjectionMatrix()
} }

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import react.child
import react.dom.render import react.dom.render
import kotlin.browser.document 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") val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
render(element) { render(element) {
child(MMAppComponent::class) { child(MMApp) {
attrs { attrs {
model = this@MMDemoApp.model model = this@MMDemoApp.model
connection = this@MMDemoApp.connection connection = this@MMDemoApp.connection

View File

@ -11,25 +11,6 @@
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script> <script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
</head> </head>
<body class="testApp"> <body class="testApp">
<div class="container-fluid" id = "app"> </div>
<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>
</body> </body>
</html> </html>