0.2.0 #71
@ -1,4 +1,8 @@
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import react.child
|
||||
import react.dom.render
|
||||
@ -17,6 +21,7 @@ import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.startApplication
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.random.Random
|
||||
|
||||
private class JsPlaygroundApp : Application {
|
||||
@ -31,9 +36,38 @@ private class JsPlaygroundApp : Application {
|
||||
|
||||
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
|
||||
|
||||
val bouncingSphere = SolidGroup {
|
||||
sphere(5.0, "ball") {
|
||||
detail = 16
|
||||
color("red")
|
||||
val h = 100.0
|
||||
y = h
|
||||
GlobalScope.launch {
|
||||
val g = 10.0
|
||||
val dt = 0.1
|
||||
var time = 0.0
|
||||
var velocity = 0.0
|
||||
while (isActive) {
|
||||
delay(20)
|
||||
time += dt
|
||||
velocity -= g * dt
|
||||
y = y.toDouble() + velocity * dt
|
||||
if (y.toDouble() <= 2.5){
|
||||
velocity = sqrt(2*g*h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
box(200, 5, 200, name = "floor"){
|
||||
y = -2.5
|
||||
}
|
||||
}
|
||||
|
||||
val visionOfD0 = GdmlShowCase.babyIaxo().toVision()
|
||||
|
||||
val random = Random(112233)
|
||||
|
||||
val visionOfSpheres = SolidGroup {
|
||||
repeat(100) {
|
||||
sphere(5, name = "sphere[$it]") {
|
||||
@ -56,7 +90,16 @@ private class JsPlaygroundApp : Application {
|
||||
height = 100.vh
|
||||
width = 100.vw
|
||||
}
|
||||
SmartTabs("D0") {
|
||||
SmartTabs("gravity") {
|
||||
Tab("gravity") {
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
solid = bouncingSphere
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tab("D0") {
|
||||
child(ThreeCanvasWithControls) {
|
||||
attrs {
|
||||
@ -73,8 +116,8 @@ private class JsPlaygroundApp : Application {
|
||||
}
|
||||
}
|
||||
}
|
||||
Tab("plotly"){
|
||||
Plotly{
|
||||
Tab("plotly") {
|
||||
Plotly {
|
||||
attrs {
|
||||
context = playgroundContext
|
||||
plot = space.kscience.plotly.Plotly.plot {
|
||||
|
@ -66,7 +66,7 @@ public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalCo
|
||||
}
|
||||
}
|
||||
propertyEditor(
|
||||
ownProperties = props.canvasOptions,
|
||||
ownProperties = props.canvasOptions.meta,
|
||||
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
|
||||
descriptor = Canvas3DOptions.descriptor,
|
||||
expanded = false
|
||||
|
@ -4,10 +4,13 @@ import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.render
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.getStyle
|
||||
import space.kscience.visionforge.react.metaViewer
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
import space.kscience.visionforge.solid.SolidReference
|
||||
import space.kscience.visionforge.styles
|
||||
|
||||
public fun RBuilder.visionPropertyEditor(
|
||||
vision: Vision,
|
||||
@ -19,7 +22,6 @@ public fun RBuilder.visionPropertyEditor(
|
||||
propertyEditor(
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
descriptor = descriptor,
|
||||
key = key
|
||||
)
|
||||
|
@ -10,7 +10,6 @@ import react.dom.attrs
|
||||
import react.dom.option
|
||||
import react.dom.select
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.string
|
||||
@ -18,19 +17,16 @@ import space.kscience.dataforge.values.string
|
||||
@JsExport
|
||||
public val MultiSelectChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("MultiSelectChooser") { props ->
|
||||
var selectedItems by useState { props.item?.value?.list ?: emptyList() }
|
||||
|
||||
val onChange: (Event) -> Unit = { event: Event ->
|
||||
val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList()
|
||||
.map { (it as HTMLOptionElement).value.asValue() }
|
||||
props.valueChanged?.invoke(newSelected.asValue())
|
||||
selectedItems = newSelected
|
||||
props.meta.value = newSelected.asValue()
|
||||
}
|
||||
|
||||
select {
|
||||
attrs {
|
||||
multiple = true
|
||||
values = selectedItems.mapTo(HashSet()) { it.string }
|
||||
values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
|
||||
onChangeFunction = onChange
|
||||
}
|
||||
props.descriptor?.allowedValues?.forEach { optionValue ->
|
||||
|
@ -1,14 +1,5 @@
|
||||
package space.kscience.visionforge.react
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.TextDecoration
|
||||
import kotlinx.html.js.onClickFunction
|
||||
@ -22,7 +13,6 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.visionforge.hidden
|
||||
import styled.css
|
||||
import styled.styledButton
|
||||
@ -32,17 +22,17 @@ import styled.styledSpan
|
||||
public external interface PropertyEditorProps : RProps {
|
||||
|
||||
/**
|
||||
* Root config object - always non null
|
||||
* Root config object - always non-null
|
||||
*/
|
||||
public var ownProperties: MutableMetaProvider
|
||||
public var meta: ObservableMutableMeta
|
||||
|
||||
/**
|
||||
* Provide default item (greyed out if used)
|
||||
*/
|
||||
public var allProperties: MetaProvider?
|
||||
public var withDefault: MetaProvider
|
||||
|
||||
/**
|
||||
* Full path to the displayed node in [ownProperties]. Could be empty
|
||||
* Full path to the displayed node in [meta]. Could be empty
|
||||
*/
|
||||
public var name: Name
|
||||
|
||||
@ -51,16 +41,6 @@ public external interface PropertyEditorProps : RProps {
|
||||
*/
|
||||
public var descriptor: MetaDescriptor?
|
||||
|
||||
/**
|
||||
* A coroutine scope for updates
|
||||
*/
|
||||
public var scope: CoroutineScope?
|
||||
|
||||
/**
|
||||
* Flow names of updated properties
|
||||
*/
|
||||
public var updateFlow: Flow<Name>?
|
||||
|
||||
/**
|
||||
* Initial expanded state
|
||||
*/
|
||||
@ -68,67 +48,59 @@ public external interface PropertyEditorProps : RProps {
|
||||
}
|
||||
|
||||
private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
|
||||
functionalComponent("ConfigEditorItem") { props ->
|
||||
functionalComponent("PropertyEditorItem") { props ->
|
||||
propertyEditorItem(props)
|
||||
}
|
||||
|
||||
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
var expanded: Boolean by useState { props.expanded ?: true }
|
||||
val descriptor: MetaDescriptor? = props.descriptor?.get(props.name)
|
||||
var ownProperty: Meta? by useState { props.ownProperties.getMeta(props.name) }
|
||||
val actualMeta = props.allProperties?.getMeta(props.name)
|
||||
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
|
||||
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
|
||||
|
||||
val keys = useMemo(descriptor) {
|
||||
buildSet {
|
||||
descriptor?.children?.filterNot {
|
||||
it.key.startsWith("@") || it.value.hidden
|
||||
}?.forEach {
|
||||
add(NameToken(it.key))
|
||||
}
|
||||
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val token = props.name.lastOrNull()?.toString() ?: "Properties"
|
||||
|
||||
fun update() {
|
||||
ownProperty = props.ownProperties.getMeta(props.name)
|
||||
ownProperty = props.meta.getOrCreate(props.name)
|
||||
}
|
||||
|
||||
if (props.updateFlow != null) {
|
||||
useEffect(props.ownProperties, props.updateFlow) {
|
||||
val updateJob = props.updateFlow!!.onEach { updatedName ->
|
||||
if (updatedName == props.name) {
|
||||
update()
|
||||
}
|
||||
}.launchIn(props.scope ?: GlobalScope)
|
||||
cleanup {
|
||||
updateJob.cancel()
|
||||
useEffect(props.meta) {
|
||||
props.meta.onChange(props) { updatedName ->
|
||||
if (updatedName == props.name) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
props.meta.removeListener(props)
|
||||
}
|
||||
}
|
||||
|
||||
val expanderClick: (Event) -> Unit = {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
val valueChanged: (Value?) -> Unit = {
|
||||
if (it == null) {
|
||||
props.ownProperties.remove(props.name)
|
||||
} else {
|
||||
props.ownProperties.setValue(props.name, it)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
val removeClick: (Event) -> Unit = {
|
||||
props.ownProperties.remove(props.name)
|
||||
props.meta.remove(props.name)
|
||||
update()
|
||||
}
|
||||
|
||||
val keys = buildSet {
|
||||
descriptor?.children?.filterNot {
|
||||
it.key.startsWith("@") || it.value.hidden
|
||||
}?.forEach {
|
||||
add(NameToken(it.key))
|
||||
}
|
||||
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
|
||||
}
|
||||
|
||||
|
||||
flexRow {
|
||||
css {
|
||||
alignItems = Align.center
|
||||
}
|
||||
if(keys.isNotEmpty()) {
|
||||
if (keys.isNotEmpty()) {
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeCaret
|
||||
@ -144,25 +116,26 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
styledSpan {
|
||||
css {
|
||||
+TreeStyles.treeLabel
|
||||
if (ownProperty == null) {
|
||||
if (ownProperty.isEmpty()) {
|
||||
+TreeStyles.treeLabelInactive
|
||||
}
|
||||
}
|
||||
+token
|
||||
}
|
||||
if(!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
|
||||
if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
|
||||
styledDiv {
|
||||
css {
|
||||
//+TreeStyles.resizeableInput
|
||||
width = 160.px
|
||||
margin(1.px, 5.px)
|
||||
}
|
||||
valueChooser(
|
||||
props.name,
|
||||
actualMeta,
|
||||
descriptor,
|
||||
valueChanged
|
||||
)
|
||||
ValueChooser{
|
||||
attrs {
|
||||
this.descriptor = descriptor
|
||||
this.meta = ownProperty
|
||||
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
styledButton {
|
||||
@ -184,7 +157,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
}
|
||||
+"\u00D7"
|
||||
attrs {
|
||||
if (ownProperty == null) {
|
||||
if (ownProperty.isEmpty()) {
|
||||
disabled = true
|
||||
} else {
|
||||
onClickFunction = removeClick
|
||||
@ -206,8 +179,8 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.ownProperties = props.ownProperties
|
||||
this.allProperties = props.allProperties
|
||||
this.meta = props.meta
|
||||
this.withDefault = props.withDefault
|
||||
this.name = props.name + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
@ -217,74 +190,52 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@JsExport
|
||||
public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = ""
|
||||
this.ownProperties = props.ownProperties
|
||||
this.allProperties = props.allProperties
|
||||
this.meta = props.meta
|
||||
this.withDefault = props.withDefault
|
||||
this.name = Name.EMPTY
|
||||
this.descriptor = props.descriptor
|
||||
this.scope = props.scope
|
||||
this.expanded = props.expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.propertyEditor(
|
||||
ownProperties: MutableMetaProvider,
|
||||
allProperties: MetaProvider? = ownProperties,
|
||||
updateFlow: Flow<Name>? = null,
|
||||
ownProperties: ObservableMutableMeta,
|
||||
allProperties: MetaProvider = ownProperties,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
key: Any? = null,
|
||||
expanded: Boolean? = null
|
||||
) {
|
||||
child(PropertyEditor) {
|
||||
attrs {
|
||||
this.ownProperties = ownProperties
|
||||
this.allProperties = allProperties
|
||||
this.updateFlow = updateFlow
|
||||
this.meta = ownProperties
|
||||
this.withDefault = allProperties
|
||||
this.descriptor = descriptor
|
||||
this.key = key?.toString() ?: ""
|
||||
this.scope = scope
|
||||
this.expanded = expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun ObservableMutableMeta.flowUpdates(): Flow<Name> = callbackFlow {
|
||||
onChange(this) { name ->
|
||||
launch {
|
||||
send(name)
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
removeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun RBuilder.configEditor(
|
||||
config: ObservableMutableMeta,
|
||||
default: MetaProvider? = null,
|
||||
default: MetaProvider = config,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
key: Any? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
|
||||
): Unit = propertyEditor(config, default, descriptor, key = key)
|
||||
|
||||
public fun Element.configEditor(
|
||||
config: ObservableMutableMeta,
|
||||
default: Meta = config,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
default: Meta? = null,
|
||||
key: Any? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = render(this) {
|
||||
configEditor(config, default, descriptor, key, scope)
|
||||
configEditor(config, default, descriptor, key = key)
|
||||
}
|
@ -10,6 +10,7 @@ import react.FunctionComponent
|
||||
import react.dom.attrs
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||
import space.kscience.dataforge.meta.double
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
@ -20,30 +21,32 @@ import styled.styledInput
|
||||
@JsExport
|
||||
public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("RangeValueChooser") { props ->
|
||||
var innerValue by useState(props.item.double)
|
||||
var rangeDisabled: Boolean by useState(props.item == null)
|
||||
var innerValue by useState(props.actual.double)
|
||||
var rangeDisabled: Boolean by useState(props.meta.value == null)
|
||||
|
||||
val handleDisable: (Event) -> Unit = {
|
||||
val checkBoxValue = (it.target as HTMLInputElement).checked
|
||||
rangeDisabled = !checkBoxValue
|
||||
if(!checkBoxValue) {
|
||||
props.valueChanged?.invoke(null)
|
||||
props.meta.value = if(!checkBoxValue) {
|
||||
null
|
||||
} else {
|
||||
props.valueChanged?.invoke(innerValue?.asValue())
|
||||
innerValue?.asValue()
|
||||
}
|
||||
}
|
||||
|
||||
val handleChange: (Event) -> Unit = {
|
||||
val newValue = (it.target as HTMLInputElement).value
|
||||
props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue())
|
||||
props.meta.value = newValue.toDoubleOrNull()?.asValue()
|
||||
innerValue = newValue.toDoubleOrNull()
|
||||
}
|
||||
|
||||
flexRow {
|
||||
styledInput(type = InputType.checkBox) {
|
||||
attrs {
|
||||
defaultChecked = rangeDisabled.not()
|
||||
onChangeFunction = handleDisable
|
||||
if(props.descriptor?.valueRequirement != ValueRequirement.REQUIRED) {
|
||||
styledInput(type = InputType.checkBox) {
|
||||
attrs {
|
||||
defaultChecked = rangeDisabled.not()
|
||||
onChangeFunction = handleDisable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +58,7 @@ public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
disabled = rangeDisabled
|
||||
value = innerValue?.toString() ?: ""
|
||||
onChangeFunction = handleChange
|
||||
consumer.onTagEvent(this, "input", handleChange)
|
||||
val minValue = props.descriptor?.attributes?.get("min").string
|
||||
minValue?.let {
|
||||
min = it
|
||||
|
@ -13,14 +13,13 @@ import org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.attrs
|
||||
import react.dom.option
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.boolean
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.*
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.int
|
||||
import space.kscience.dataforge.values.string
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.widgetType
|
||||
import styled.css
|
||||
@ -28,23 +27,19 @@ import styled.styledInput
|
||||
import styled.styledSelect
|
||||
|
||||
public external interface ValueChooserProps : RProps {
|
||||
public var item: Meta?
|
||||
public var descriptor: MetaDescriptor?
|
||||
|
||||
//public var nullable: Boolean?
|
||||
public var valueChanged: ((Value?) -> Unit)?
|
||||
public var meta: ObservableMutableMeta
|
||||
public var actual: Meta
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val StringValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("StringValueChooser") { props ->
|
||||
var value by useState(props.item.string ?: "")
|
||||
var value by useState(props.actual.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
value = (event.target as HTMLInputElement).value
|
||||
if (value != props.item.string) {
|
||||
props.valueChanged?.invoke(value.asValue())
|
||||
}
|
||||
props.meta.value = value.asValue()
|
||||
}
|
||||
}
|
||||
val handleChange: (Event) -> Unit = {
|
||||
@ -67,7 +62,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("BooleanValueChooser") { props ->
|
||||
val handleChange: (Event) -> Unit = {
|
||||
val newValue = (it.target as HTMLInputElement).checked
|
||||
props.valueChanged?.invoke(newValue.asValue())
|
||||
props.meta.value = newValue.asValue()
|
||||
}
|
||||
styledInput(type = InputType.checkBox) {
|
||||
css {
|
||||
@ -75,7 +70,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
}
|
||||
attrs {
|
||||
//this.attributes["indeterminate"] = (props.item == null).toString()
|
||||
defaultChecked = props.item.boolean ?: false
|
||||
defaultChecked = props.actual.boolean ?: false
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
@ -84,7 +79,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
@JsExport
|
||||
public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("NumberValueChooser") { props ->
|
||||
var innerValue by useState(props.item.string ?: "")
|
||||
var innerValue by useState(props.actual.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
innerValue = (event.target as HTMLInputElement).value
|
||||
@ -92,7 +87,7 @@ public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
if (number == null) {
|
||||
console.error("The input value $innerValue is not a number")
|
||||
} else {
|
||||
props.valueChanged?.invoke(number.asValue())
|
||||
props.meta.value = number.asValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,10 +118,10 @@ public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
@JsExport
|
||||
public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("ComboValueChooser") { props ->
|
||||
var selected by useState(props.item.string ?: "")
|
||||
var selected by useState(props.actual.string ?: "")
|
||||
val handleChange: (Event) -> Unit = {
|
||||
selected = (it.target as HTMLSelectElement).value
|
||||
props.valueChanged?.invoke(selected.asValue())
|
||||
props.meta.value = selected.asValue()
|
||||
}
|
||||
styledSelect {
|
||||
css {
|
||||
@ -138,7 +133,7 @@ public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
}
|
||||
}
|
||||
attrs {
|
||||
this.value = props.item?.string ?: ""
|
||||
this.value = props.actual.string ?: ""
|
||||
multiple = false
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
@ -149,14 +144,14 @@ public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
public val ColorValueChooser: FunctionComponent<ValueChooserProps> =
|
||||
functionalComponent("ColorValueChooser") { props ->
|
||||
var value by useState(
|
||||
props.item?.value?.let { value ->
|
||||
props.actual.value?.let { value ->
|
||||
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
||||
else value.string
|
||||
} ?: "#000000"
|
||||
)
|
||||
val handleChange: (Event) -> Unit = {
|
||||
value = (it.target as HTMLInputElement).value
|
||||
props.valueChanged?.invoke(value.asValue())
|
||||
props.meta.value = value.asValue()
|
||||
}
|
||||
styledInput(type = InputType.color) {
|
||||
css {
|
||||
@ -189,19 +184,3 @@ public val ValueChooser: FunctionComponent<ValueChooserProps> = functionalCompon
|
||||
else -> child(StringValueChooser, props)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RBuilder.valueChooser(
|
||||
name: Name,
|
||||
item: Meta?,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
callback: (Value?) -> Unit,
|
||||
) {
|
||||
child(ValueChooser) {
|
||||
attrs {
|
||||
key = name.toString()
|
||||
this.item = item
|
||||
this.descriptor = descriptor
|
||||
this.valueChanged = callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.dataforge.names.length
|
||||
import space.kscience.visionforge.VisionGroup
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.propertyChanges
|
||||
import space.kscience.visionforge.react.ThreeCanvasComponent
|
||||
import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.flexRow
|
||||
@ -137,7 +136,6 @@ public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsPro
|
||||
propertyEditor(
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
descriptor = vision.descriptor,
|
||||
key = selected
|
||||
)
|
||||
|
@ -8,11 +8,14 @@ import ringui.Island
|
||||
import ringui.SmartTabs
|
||||
import ringui.Tab
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.computeProperties
|
||||
import space.kscience.visionforge.getStyle
|
||||
import space.kscience.visionforge.react.flexColumn
|
||||
import space.kscience.visionforge.react.metaViewer
|
||||
import space.kscience.visionforge.react.propertyEditor
|
||||
import space.kscience.visionforge.solid.SolidReference
|
||||
import space.kscience.visionforge.styles
|
||||
|
||||
public fun RBuilder.ringPropertyEditor(
|
||||
vision: Vision,
|
||||
@ -30,7 +33,6 @@ public fun RBuilder.ringPropertyEditor(
|
||||
propertyEditor(
|
||||
ownProperties = vision.meta,
|
||||
allProperties = vision.computeProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
descriptor = descriptor,
|
||||
key = key
|
||||
)
|
||||
|
@ -72,7 +72,7 @@ internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functional
|
||||
}
|
||||
}
|
||||
propertyEditor(
|
||||
ownProperties = props.options,
|
||||
ownProperties = props.options.meta,
|
||||
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
|
||||
descriptor = Canvas3DOptions.descriptor,
|
||||
expanded = false
|
||||
|
@ -12,10 +12,12 @@ import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.boolean
|
||||
import space.kscience.visionforge.Vision.Companion.TYPE
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
/**
|
||||
* A root type for display hierarchy
|
||||
@ -129,3 +131,17 @@ public var Vision.visible: Boolean?
|
||||
get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
|
||||
set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
|
||||
|
||||
|
||||
public fun <V : Vision, T> V.useProperty(
|
||||
property: KProperty1<V, T>,
|
||||
owner: Any? = null,
|
||||
callBack: V.(T) -> Unit,
|
||||
) {
|
||||
//Pass initial value.
|
||||
callBack(property.get(this))
|
||||
meta.onChange(owner) { name ->
|
||||
if (name.startsWith(property.name.asName())) {
|
||||
callBack(property.get(this@useProperty))
|
||||
}
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ public open class VisionBase(
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<MetaListener>()
|
||||
private val listeners: MutableList<MetaListener> = mutableListOf()
|
||||
|
||||
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
|
||||
|
||||
|
@ -11,7 +11,6 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionBase
|
||||
import space.kscience.visionforge.setProperty
|
||||
|
||||
@Serializable
|
||||
@SerialName("vision.markup")
|
||||
@ -20,15 +19,11 @@ public class VisionOfMarkup(
|
||||
) : VisionBase() {
|
||||
|
||||
//FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix
|
||||
override var properties: MutableMeta? = null
|
||||
protected override var properties: MutableMeta? = null
|
||||
|
||||
//TODO add templates
|
||||
|
||||
public var content: String?
|
||||
get() = meta.getMeta(CONTENT_PROPERTY_KEY).string
|
||||
set(value) {
|
||||
setProperty(CONTENT_PROPERTY_KEY, value)
|
||||
}
|
||||
public var content: String? by meta.string(CONTENT_PROPERTY_KEY)
|
||||
|
||||
public companion object {
|
||||
public val CONTENT_PROPERTY_KEY: Name = "content".asName()
|
||||
|
@ -1,16 +1,19 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.w3c.dom.Element
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.visionforge.ElementVisionRenderer
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionClient
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT
|
||||
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
@ -26,8 +29,20 @@ public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||
require(vision is VisionOfMarkup) { "The vision is not a markup vision" }
|
||||
val div = document.createElement("div")
|
||||
val flavour = when (vision.format) {
|
||||
COMMONMARK_FORMAT -> CommonMarkFlavourDescriptor()
|
||||
GFM_FORMAT -> GFMFlavourDescriptor()
|
||||
//TODO add new formats via plugins
|
||||
else-> error("Format ${vision.format} not recognized")
|
||||
}
|
||||
vision.useProperty(VisionOfMarkup::content) {
|
||||
div.clear()
|
||||
div.append {
|
||||
markdown(flavour) { vision.content ?: "" }
|
||||
|
||||
}
|
||||
}
|
||||
element.append(div)
|
||||
TODO()
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<MarkupPlugin> {
|
||||
|
@ -5,7 +5,6 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.scheme
|
||||
import space.kscience.dataforge.meta.descriptors.value
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.visionforge.hide
|
||||
import space.kscience.visionforge.widgetType
|
||||
|
||||
@ -21,20 +20,21 @@ public class Clipping : Scheme() {
|
||||
attributes["min"] = 0.0
|
||||
attributes["max"] = 1.0
|
||||
attributes["step"] = 0.01
|
||||
default(1.0)
|
||||
}
|
||||
value(Clipping::y) {
|
||||
widgetType = "range"
|
||||
attributes["min"] = 0.0
|
||||
attributes["max"] = 1.0
|
||||
attributes["step"] = 0.01
|
||||
|
||||
default(1.0)
|
||||
}
|
||||
value(Clipping::z) {
|
||||
widgetType = "range"
|
||||
attributes["min"] = 0.0
|
||||
attributes["max"] = 1.0
|
||||
attributes["step"] = 0.01
|
||||
|
||||
default(1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,42 +168,40 @@ public class ThreeCanvas(
|
||||
}
|
||||
|
||||
//Clipping planes
|
||||
options.meta.onChange(this@ThreeCanvas) { name->
|
||||
if (name.startsWith(Canvas3DOptions::clipping.name.asName())) {
|
||||
val clipping = options.clipping
|
||||
if (!clipping.meta.isEmpty()) {
|
||||
renderer.localClippingEnabled = true
|
||||
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 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)
|
||||
}
|
||||
renderer.clippingPlanes =
|
||||
listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray()
|
||||
options.useProperty(Canvas3DOptions::clipping){clipping ->
|
||||
if (!clipping.meta.isEmpty()) {
|
||||
renderer.localClippingEnabled = true
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
renderer.localClippingEnabled = false
|
||||
}
|
||||
} else if (name.startsWith(Canvas3DOptions::size.name.asName())) {
|
||||
canvas.style.apply {
|
||||
minWidth = "${options.size.minWith.toInt()}px"
|
||||
maxWidth = "${options.size.maxWith.toInt()}px"
|
||||
minHeight = "${options.size.minHeight.toInt()}px"
|
||||
maxHeight = "${options.size.maxHeight.toInt()}px"
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
renderer.clippingPlanes =
|
||||
listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray()
|
||||
}
|
||||
} else {
|
||||
renderer.localClippingEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
options.useProperty(Canvas3DOptions::size){
|
||||
canvas.style.apply {
|
||||
minWidth = "${options.size.minWith.toInt()}px"
|
||||
maxWidth = "${options.size.maxWith.toInt()}px"
|
||||
minHeight = "${options.size.minHeight.toInt()}px"
|
||||
maxHeight = "${options.size.maxHeight.toInt()}px"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user