Fix crash with single style properties

This commit is contained in:
Alexander Nozik 2021-06-08 18:45:03 +03:00
parent bd1f7d75fc
commit dfc0ffda38
23 changed files with 331 additions and 159 deletions

View File

@ -1,17 +1,20 @@
package space.kscience.visionforge.gdml.demo
import kotlinx.browser.window
import kotlinx.css.*
import kotlinx.css.height
import kotlinx.css.vh
import org.w3c.files.FileReader
import org.w3c.files.get
import react.*
import react.dom.h1
import ringui.grid.ringCol
import ringui.grid.ringGrid
import ringui.grid.ringRow
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromString
import space.kscience.visionforge.bootstrap.gridRow
import space.kscience.visionforge.bootstrap.nameCrumbs
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.react.ThreeCanvasComponent
@ -21,7 +24,6 @@ import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
external interface GDMLAppProps : RProps {
var context: Context
@ -62,54 +64,57 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
vision = parsedVision as? Solid ?: error("Parsed vision is not a solid")
}
gridRow {
flexColumn {
css {
+"col-lg-9"
height = 100.vh
}
styledDiv {
css {
+"mx-auto"
+"page-header"
}
h1 { +"GDML/JSON loader demo" }
}
nameCrumbs(selected, "World", onSelect)
//canvas
child(ThreeCanvasComponent) {
ringGrid {
ringRow {
ringCol {
attrs {
this.context = props.context
this.obj = vision
this.selected = selected
this.options = options
lg = 9
}
}
flexColumn {
css {
height = 100.vh
}
h1 { +"GDML/JSON loader demo" }
//canvas
}
flexColumn {
css {
+"col-lg-3"
padding(top = 4.px)
//border(1.px, BorderStyle.solid, Color.lightGray)
height = 100.vh
overflowY = Overflow.auto
}
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
if (file != null) {
FileReader().apply {
onload = {
val string = result as String
loadData(file.name, string)
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = vision
this.selected = selected
this.options = options
}
readAsText(file)
}
}
}
ringCol {
attrs {
lg = 3
}
flexColumn {
css {
height = 100.vh
}
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
if (file != null) {
FileReader().apply {
onload = {
val string = result as String
loadData(file.name, string)
}
readAsText(file)
}
}
}
nameCrumbs(selected, "World", onSelect)
ringThreeControls(options, vision, selected, onSelect)
}
}
ringThreeControls(options, vision, selected, onSelect)
}
}
}

View File

@ -14,6 +14,7 @@ fun RBuilder.fileDrop(title: String, action: (files: FileList?) -> Unit) {
styledDiv {
css {
border(style = BorderStyle.dashed, width = 1.px, color = Color.orange)
flexGrow = 0.0
alignContent = Align.center
}

View File

@ -0,0 +1,27 @@
plugins {
id("ru.mipt.npm.gradle.js")
}
kscience{
useCoroutines()
application()
}
kotlin{
js(IR){
useCommonJs()
browser {
commonWebpackConfig {
cssSupport.enabled = false
}
}
}
}
dependencies{
implementation(project(":visionforge-gdml"))
implementation(project(":visionforge-plotly"))
implementation(project(":visionforge-threejs"))
implementation(project(":ui:ring"))
}

View File

@ -0,0 +1,51 @@
import kotlinx.browser.document
import kotlinx.css.height
import kotlinx.css.vh
import kotlinx.css.vw
import kotlinx.css.width
import react.child
import react.dom.render
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionClient
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.ThreeWithControls
import space.kscience.visionforge.startApplication
import styled.css
import styled.styledDiv
private class JsPlaygroundApp : Application {
override fun start(state: Map<String, Any>) {
val playgroundContext = Context {
plugin(ThreeWithControls)
plugin(VisionClient)
}
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
val visionOfD0 = GdmlShowCase.babyIaxo().toVision()
render(element) {
styledDiv {
css{
height = 100.vh
width = 100.vw
}
child(ThreeCanvasWithControls) {
attrs {
context = playgroundContext
solid = visionOfD0
}
}
}
}
}
}
public fun main() {
startApplication(::JsPlaygroundApp)
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-playground</title>
<script src="js-playground.js"></script>
</head>
<body>
<div id="playground"></div>
</body>
</html>

View File

@ -0,0 +1,3 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -34,14 +34,6 @@ external interface MMAppProps : RProps {
var selected: Name?
}
private val canvasConfig = Canvas3DOptions {
camera = Camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
}
@JsExport
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
var selected by useState { props.selected }
@ -50,8 +42,13 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
selected = it
}
val options = useMemo {
Canvas3DOptions.invoke {
val mmOptions = useMemo {
Canvas3DOptions {
camera = Camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
this.onSelect = onSelect
}
}
@ -91,11 +88,9 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = root
this.solid = root
this.selected = selected
this.options = canvasConfig.apply {
this.onSelect = onSelect
}
this.options = mmOptions
}
}
}
@ -112,7 +107,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
}
//settings
card("Canvas configuration") {
canvasControls(options, root)
canvasControls(mmOptions, root)
}
card("Events") {
@ -151,7 +146,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
+"World"
attrs {
onClickFunction = {
selected = space.kscience.dataforge.names.Name.EMPTY
selected = Name.EMPTY
}
}
}

View File

@ -6,6 +6,7 @@ import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.browser.document
import react.child
import react.dom.render
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application
@ -29,7 +30,7 @@ private class MMDemoApp : Application {
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
val context = Global.buildContext("demo") {}
val context = Context("demo")
render(element) {
child(MMApp) {
attrs {

View File

@ -3,7 +3,6 @@ package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids
fun main() {
@ -11,7 +10,7 @@ fun main() {
plugin(Solids)
}
context.makeVisionFile( resourceLocation = ResourceLocation.EMBED) {
context.makeVisionFile {
vision("canvas") { GdmlShowCase.babyIaxo().toVision() }
}
}

View File

@ -46,5 +46,6 @@ include(
":demo:playground",
":demo:jupyter-playground",
":demo:plotly-fx",
":demo:js-playground",
":jupyter:visionforge-gdml-jupyter"
)
)

View File

@ -24,6 +24,11 @@ public external interface MetaViewerProps : RProps {
*/
public var root: Meta
/**
* The title of root node
*/
public var rootName: String?
/**
* Full path to the displayed node in [root]. Could be empty
*/
@ -45,7 +50,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val actualItem = item ?: descriptorItem?.defaultValue
val token = props.name.lastOrNull()?.toString() ?: "Meta"
val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: ""
val expanderClick: (Event) -> Unit = {
expanded = !expanded

View File

@ -231,6 +231,7 @@ public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functional
this.name = Name.EMPTY
this.descriptor = props.descriptor
this.scope = props.scope
this.expanded = props.expanded
}
}
}

View File

@ -1,5 +1,8 @@
package space.kscience.visionforge.react
import kotlinx.css.margin
import kotlinx.css.padding
import kotlinx.css.px
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement
@ -8,36 +11,63 @@ import react.FunctionalComponent
import react.dom.attrs
import react.functionalComponent
import react.useState
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.values.asValue
import styled.css
import styled.styledInput
@JsExport
public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
functionalComponent("RangeValueChooser") { props ->
var innerValue by useState(props.item.string)
var innerValue by useState(props.item.double)
var rangeDisabled: Boolean by useState(props.item == null)
val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue
if(!checkBoxValue) {
props.valueChanged?.invoke(null)
} else {
props.valueChanged?.invoke(innerValue?.asValue())
}
}
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value
props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue())
innerValue = newValue
innerValue = newValue.toDoubleOrNull()
}
styledInput(type = InputType.range) {
attrs {
value = innerValue ?: ""
onChangeFunction = handleChange
val minValue = props.descriptor?.attributes?.get("min").string
minValue?.let {
min = it
flexRow {
styledInput(type = InputType.checkBox) {
css{
padding(0.px)
margin(0.px)
}
val maxValue = props.descriptor?.attributes?.get("max").string
maxValue?.let {
max = it
attrs {
defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable
}
props.descriptor?.attributes?.get("step").string?.let {
step = it
}
styledInput(type = InputType.range) {
attrs {
disabled = rangeDisabled
value = innerValue?.toString() ?: ""
onChangeFunction = handleChange
val minValue = props.descriptor?.attributes?.get("min").string
minValue?.let {
min = it
}
val maxValue = props.descriptor?.attributes?.get("max").string
maxValue?.let {
max = it
}
props.descriptor?.attributes?.get("step").string?.let {
step = it
}
}
}
}

View File

@ -1,9 +1,8 @@
package space.kscience.visionforge.react
import kotlinx.css.Display
import kotlinx.css.display
import kotlinx.css.height
import kotlinx.css.pct
import kotlinx.css.FlexBasis
import kotlinx.css.flexBasis
import kotlinx.css.flexGrow
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import react.*
@ -19,8 +18,8 @@ import styled.styledDiv
public external interface ThreeCanvasProps : RProps {
public var context: Context
public var options: Canvas3DOptions
public var obj: Solid?
public var options: Canvas3DOptions?
public var solid: Solid?
public var selected: Name?
}
@ -37,15 +36,15 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
val three: ThreePlugin = useMemo({ props.context.fetch(ThreePlugin) }, arrayOf(props.context))
useEffect(listOf(props.obj, props.options, elementRef)) {
useEffect(listOf(props.solid, props.options, elementRef)) {
if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
canvas = three.getOrCreateCanvas(element, props.options)
canvas = three.getOrCreateCanvas(element, props.options ?: Canvas3DOptions())
}
}
useEffect(listOf(canvas, props.obj)) {
props.obj?.let { obj ->
useEffect(listOf(canvas, props.solid)) {
props.solid?.let { obj ->
canvas?.render(obj)
}
}
@ -56,8 +55,8 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
styledDiv {
css {
display = Display.contents
height = 100.pct
flexGrow = 1.0
flexBasis = FlexBasis.fill
}
ref = elementRef
}

View File

@ -21,6 +21,7 @@ import styled.styledSelect
public external interface ValueChooserProps : RProps {
public var item: MetaItem?
public var descriptor: ValueDescriptor?
public var nullable: Boolean?
public var valueChanged: ((Value?) -> Unit)?
}

View File

@ -4,14 +4,14 @@ import react.RBuilder
import react.RHandler
import react.dom.WithClassName
public external interface SmartTabsProps: WithClassName {
public external interface SmartTabsProps : WithClassName {
public var initSelected: String
}
public fun RBuilder.ringSmartTabs(active: String? = null, handler: RHandler<SmartTabsProps>){
TabsModule.SmartTabs{
active?.let{
public fun RBuilder.ringSmartTabs(active: String? = null, handler: RHandler<SmartTabsProps>) {
TabsModule.SmartTabs {
active?.let {
attrs {
initSelected = active
}

View File

@ -40,10 +40,11 @@ public fun RBuilder.ringTabs(active: String? = null, handler: RHandler<TabsProps
}
}
public fun RBuilder.ringTab(title: dynamic, handler: RHandler<TabProps>) {
public fun RBuilder.ringTab(title: dynamic, id: String = title.toString(), handler: RHandler<TabProps>) {
TabsModule.Tab {
attrs {
this.title = title
this.id = id
}
handler()
}

View File

@ -7,21 +7,20 @@ import ringui.grid.ringGrid
import ringui.grid.ringRow
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
public external interface ThreeViewWithControlsProps : RProps {
public external interface ThreeCanvasWithControlsProps : RProps {
public var context: Context
public var vision: Vision?
public var solid: Solid?
public var selected: Name?
}
@JsExport
public val ThreeViewWithControls: (props: ThreeViewWithControlsProps) -> dynamic =
public val ThreeCanvasWithControls: (props: ThreeCanvasWithControlsProps) -> dynamic =
functionalComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
val onSelect: (Name?) -> Unit = {
@ -51,7 +50,7 @@ public val ThreeViewWithControls: (props: ThreeViewWithControlsProps) -> dynamic
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = props.vision as? Solid
this.solid = props.solid as? Solid
this.selected = selected
this.options = options
}
@ -72,7 +71,7 @@ public val ThreeViewWithControls: (props: ThreeViewWithControlsProps) -> dynamic
height = 100.pct
overflowY = Overflow.auto
}
ringThreeControls(options, props.vision, selected, onSelect)
ringThreeControls(options, props.solid, selected, onSelect)
}
}
}

View File

@ -25,10 +25,10 @@ public class ThreeWithControls : AbstractPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) {
react.dom.render(element) {
child(ThreeViewWithControls) {
child(ThreeCanvasWithControls) {
attrs {
this.context = this@ThreeWithControls.context
this.vision = vision
this.solid = vision as? Solid
}
}
}

View File

@ -2,7 +2,9 @@ package space.kscience.visionforge.ring
import org.w3c.dom.Element
import react.RBuilder
import react.dom.p
import react.dom.render
import react.useMemo
import ringui.island.ringIsland
import ringui.island.ringIslandContent
import ringui.island.ringIslandHeader
@ -10,6 +12,7 @@ import ringui.tabs.ringSmartTabs
import ringui.tabs.ringTab
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.visionforge.*
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference
@ -20,37 +23,55 @@ public fun RBuilder.ringPropertyEditor(
key: Any? = null,
) {
ringIsland("Properties") {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
updateFlow = vision.propertyChanges,
descriptor = descriptor,
key = key
)
val styles = useMemo(vision, key) {
if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct()
} else {
vision.styles
}
}
val styles = if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct()
} else {
vision.styles
}
if (styles.isNotEmpty()) {
ringIsland {
ringIslandHeader {
attrs {
border = true
flexColumn {
ringIsland("Properties") {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
updateFlow = vision.propertyChanges,
descriptor = descriptor,
key = key
)
}
if (styles.isNotEmpty()) {
ringIsland {
ringIslandHeader {
attrs {
border = true
}
+"Styles"
}
+"Styles"
}
ringIslandContent {
ringSmartTabs {
styles.forEach { styleName ->
ringIslandContent {
if (styles.size == 1) {
val styleName = styles.first()
p{
+styleName
}
val style = vision.getStyle(styleName)
if (style != null) {
ringTab(styleName) {
ringTab(styleName, id = styleName) {
metaViewer(style)
}
}
} else {
ringSmartTabs {
styles.forEach { styleName ->
val style = vision.getStyle(styleName)
if (style != null) {
ringTab(styleName, id = styleName) {
metaViewer(style)
}
}
}
}
}
}
}

View File

@ -92,22 +92,25 @@ public external interface ThreeControlsProps : RProps {
@JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
ringSmartTabs(if (props.selected != null) "Properties" else null) {
ringSmartTabs(if (props.selected != null) "Properties" else "Tree") {
ringTab("Canvas") {
ringIsland("Canvas configuration") {
canvasControls(props.canvasOptions, props.vision)
}
}
ringTab("Tree") {
styledDiv {
flexColumn {
css {
border(1.px, BorderStyle.solid, Color.lightGray)
padding(10.px)
flexGrow = 1.0
flexWrap = FlexWrap.wrap
}
h2 { +"Object tree" }
styledDiv {
css {
flex(1.0, 1.0, FlexBasis.inherit)
overflowY = Overflow.auto
flexGrow = 1.0
}
props.vision?.let {
objectTree(it, props.selected, props.onSelect)
@ -115,15 +118,17 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
}
}
}
ringTab("Properties") {
props.selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected)
}
if (selectedObject != null) {
ringPropertyEditor(selectedObject, key = selected)
if (props.selected != null) {
ringTab("Properties") {
props.selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected)
}
if (selectedObject != null) {
ringPropertyEditor(selectedObject, key = selected)
}
}
}
}

View File

@ -11,7 +11,7 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.visionforge.*
public interface SolidReference : Vision {
public interface SolidReference : Solid {
public val prototype: Solid
}
@ -76,11 +76,7 @@ public class SolidReferenceGroup(
return if (name.isEmpty()) prototype else {
val proto = (prototype as? SolidGroup)?.get(name)
?: error("Prototype with name $name not found in SolidReferenceGroup $refName")
when (proto) {
is Solid -> proto
is SolidReference -> proto.prototype
else -> error("Prototype with name $name is ${proto::class} but expected Solid")
}
proto as? Solid ?: error("Prototype with name $name is ${proto::class} but expected Solid")
}
}
@ -100,6 +96,22 @@ public class SolidReferenceGroup(
*/
private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup {
//TODO replace by properties
override var position: Point3D?
get() = prototype.position
set(value) {
error("Can't set position of reference")
}
override var rotation: Point3D?
get() = prototype.rotation
set(value) {
error("Can't set position of reference")
}
override var scale: Point3D?
get() = prototype.scale
set(value) {
error("Can't set position of reference")
}
override val prototype: Solid get() = prototypeFor(childName)
override val children: Map<NameToken, Vision>

View File

@ -103,27 +103,31 @@ public class ThreeCanvas(
}.apply {
setClearColor(Colors.skyblue, 1)
//Clipping planes
localClippingEnabled = true
options.onChange(this@ThreeCanvas) { name, _, _ ->
if (name.startsWith(Canvas3DOptions::clipping.name.asName())) {
localClippingEnabled = true
val clipping = options.clipping
boundingBox?.let { boundingBox ->
val xClippingPlane = clipping.x?.let {
val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it
Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue)
if(!clipping.isEmpty()) {
boundingBox?.let { boundingBox ->
val xClippingPlane = clipping.x?.let {
val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it
Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue)
}
val yClippingPlane = clipping.y?.let {
val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it
Plane(Vector3(0.0, -1.0, 0.0), absoluteValue)
}
}
val yClippingPlane = clipping.y?.let {
val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it
Plane(Vector3(0.0, -1.0, 0.0), absoluteValue)
}
val zClippingPlane = clipping.z?.let {
val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it
Plane(Vector3(0.0, 0.0, -1.0), absoluteValue)
val zClippingPlane = clipping.z?.let {
val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it
Plane(Vector3(0.0, 0.0, -1.0), absoluteValue)
}
clippingPlanes = listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray()
}
clippingPlanes = listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray()
}
} else {
localClippingEnabled = false
}
}
}