diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt
index 29be7aca..2740b1e5 100644
--- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt
+++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/NameCrumbs.kt
@@ -17,7 +17,7 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
classes("breadcrumb")
style {
property("--bs-breadcrumb-divider", "'.'")
- property("--bs-breadcrumb-item-padding-x",".2rem")
+ property("--bs-breadcrumb-item-padding-x",".1rem")
}
}) {
Li({
diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt
index 0892b9af..cff16298 100644
--- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt
+++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/PropertyEditor.kt
@@ -1,16 +1,16 @@
package space.kscience.visionforge.compose
import androidx.compose.runtime.*
+import app.softwork.bootstrapcompose.CloseButton
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
-import org.jetbrains.compose.web.attributes.disabled
import org.jetbrains.compose.web.css.AlignItems
import org.jetbrains.compose.web.css.alignItems
import org.jetbrains.compose.web.css.px
import org.jetbrains.compose.web.css.width
-import org.jetbrains.compose.web.dom.Button
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text
@@ -35,13 +35,13 @@ public sealed class EditorPropertyState {
}
/**
- * @param meta Root config object - always non-null
- * @param rootDescriptor Full path to the displayed node in [meta]. Could be empty
+ * @param rootMeta Root config object - always non-null
+ * @param rootDescriptor Full path to the displayed node in [rootMeta]. Could be empty
*/
@Composable
public fun PropertyEditor(
scope: CoroutineScope,
- meta: MutableMeta,
+ rootMeta: MutableMeta,
getPropertyState: (Name) -> EditorPropertyState,
updates: Flow,
name: Name = Name.EMPTY,
@@ -50,11 +50,11 @@ public fun PropertyEditor(
) {
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
val descriptor: MetaDescriptor? = remember(rootDescriptor, name) { rootDescriptor?.get(name) }
- var property: MutableMeta by remember { mutableStateOf(meta.getOrCreate(name)) }
+ var property: MutableMeta by remember { mutableStateOf(rootMeta.getOrCreate(name)) }
var editorPropertyState: EditorPropertyState by remember { mutableStateOf(getPropertyState(name)) }
- val keys = remember(descriptor) {
+ val keys by derivedStateOf {
buildSet {
descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden
@@ -68,11 +68,11 @@ public fun PropertyEditor(
val token = name.lastOrNull()?.toString() ?: "Properties"
fun update() {
- property = meta.getOrCreate(name)
+ property = rootMeta.getOrCreate(name)
editorPropertyState = getPropertyState(name)
}
- LaunchedEffect(meta) {
+ LaunchedEffect(rootMeta) {
updates.collect { updatedName ->
if (updatedName == name) {
update()
@@ -116,18 +116,9 @@ public fun PropertyEditor(
}
}
- Button({
- classes(TreeStyles.propertyEditorButton)
- if (editorPropertyState != EditorPropertyState.Defined) {
- disabled()
- } else {
- onClick {
- meta.remove(name)
- update()
- }
- }
- }) {
- Text("\u00D7")
+ CloseButton(editorPropertyState != EditorPropertyState.Defined){
+ rootMeta.remove(name)
+ update()
}
}
}
@@ -139,7 +130,7 @@ public fun PropertyEditor(
Div({
classes(TreeStyles.treeItem)
}) {
- PropertyEditor(scope, meta, getPropertyState, updates, name + token, descriptor, expanded)
+ PropertyEditor(scope, rootMeta, getPropertyState, updates, name + token, rootDescriptor, expanded)
}
}
}
@@ -155,7 +146,7 @@ public fun PropertyEditor(
) {
PropertyEditor(
scope = scope,
- meta = properties,
+ rootMeta = properties,
getPropertyState = { name ->
if (properties[name] != null) {
EditorPropertyState.Defined
@@ -172,9 +163,7 @@ public fun PropertyEditor(
}
}
- invokeOnClose {
- properties.removeListener(scope)
- }
+ awaitClose { properties.removeListener(scope) }
},
name = Name.EMPTY,
rootDescriptor = descriptor,
diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt
index ce953304..6073cdda 100644
--- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt
+++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/Tabs.kt
@@ -4,6 +4,7 @@ import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Card
import app.softwork.bootstrapcompose.NavbarLink
import app.softwork.bootstrapcompose.Styling
+import org.jetbrains.compose.web.css.overflowY
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLDivElement
@@ -51,6 +52,11 @@ public fun Tabs(
}
}
}
+ },
+ bodyAttrs = {
+ style {
+ overflowY("auto")
+ }
}
) {
activeTab?.content?.invoke(this)
diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt
index 2de59f90..39a4d669 100644
--- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt
+++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/VisionTree.kt
@@ -4,7 +4,6 @@ import androidx.compose.runtime.*
import org.jetbrains.compose.web.css.Color
import org.jetbrains.compose.web.css.color
import org.jetbrains.compose.web.css.cursor
-import org.jetbrains.compose.web.css.textDecorationLine
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text
@@ -15,8 +14,6 @@ import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence
-import space.kscience.visionforge.compose.TreeStyles.hover
-import space.kscience.visionforge.compose.TreeStyles.invoke
import space.kscience.visionforge.isEmpty
@@ -35,10 +32,6 @@ private fun TreeLabel(
style {
color(Color("#069"))
cursor("pointer")
- hover.invoke {
- textDecorationLine("underline")
- }
-
}
onClick { clickCallback(name) }
}) {
diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt
index cf1ad9f8..f0f2451c 100644
--- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt
+++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/valueChooser.kt
@@ -4,14 +4,10 @@ package space.kscience.visionforge.compose
import androidx.compose.runtime.*
import org.jetbrains.compose.web.attributes.*
-import org.jetbrains.compose.web.css.percent
-import org.jetbrains.compose.web.css.px
-import org.jetbrains.compose.web.css.width
import org.jetbrains.compose.web.dom.Input
import org.jetbrains.compose.web.dom.Option
import org.jetbrains.compose.web.dom.Select
import org.jetbrains.compose.web.dom.Text
-import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.asList
import space.kscience.dataforge.meta.*
@@ -29,20 +25,16 @@ public fun StringValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
- var stringValue by remember { mutableStateOf(value?.string ?: "") }
+ var stringValue by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
Input(type = InputType.Text) {
- style {
- width(100.percent)
- }
+ classes("w-100")
value(stringValue)
- onKeyDown { event ->
- if (event.type == "keydown" && event.asDynamic().key == "Enter") {
- stringValue = (event.target as HTMLInputElement).value
- onValueChange(stringValue.asValue())
- }
+ onChange { event ->
+ stringValue = event.value
}
- onChange {
- stringValue = it.target.value
+ onInput { event ->
+ stringValue = event.value
+ onValueChange(event.value.asValue())
}
}
}
@@ -55,16 +47,18 @@ public fun BooleanValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
+ var innerValue by remember(value, descriptor) {
+ mutableStateOf(
+ value?.boolean ?: descriptor?.defaultValue?.boolean
+ )
+ }
Input(type = InputType.Checkbox) {
- style {
- width(100.percent)
- }
- //this.attributes["indeterminate"] = (props.item == null).toString()
- checked(value?.boolean ?: false)
+ classes("w-100")
+ checked(innerValue ?: false)
- onChange {
- val newValue = it.target.checked
- onValueChange(newValue.asValue())
+ onInput { event ->
+ innerValue = event.value
+ onValueChange(event.value.asValue())
}
}
}
@@ -76,25 +70,18 @@ public fun NumberValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
- var innerValue by remember { mutableStateOf(value?.string ?: "") }
+ var innerValue by remember(value, descriptor) { mutableStateOf(value?.number) }
Input(type = InputType.Number) {
- style {
- width(100.percent)
+ classes("w-100")
+
+ value(innerValue ?: descriptor?.defaultValue?.number ?: 0.0)
+
+ onChange { event ->
+ innerValue = event.value
}
- value(innerValue)
- onKeyDown { event ->
- if (event.type == "keydown" && event.asDynamic().key == "Enter") {
- innerValue = (event.target as HTMLInputElement).value
- val number = innerValue.toDoubleOrNull()
- if (number == null) {
- console.error("The input value $innerValue is not a number")
- } else {
- onValueChange(number.asValue())
- }
- }
- }
- onChange {
- innerValue = it.target.value
+ onInput { event ->
+ innerValue = event.value
+ onValueChange(event.value?.asValue())
}
descriptor?.attributes?.get("step").number?.let {
step(it)
@@ -116,11 +103,10 @@ public fun ComboValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
- var selected by remember { mutableStateOf(value?.string ?: "") }
+ var selected by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
Select({
- style {
- width(100.percent)
- }
+ classes("w-100")
+
onChange {
selected = it.target.value
onValueChange(selected.asValue())
@@ -142,11 +128,11 @@ public fun ColorValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
+ var innerValue by remember { mutableStateOf(value?.string ?: descriptor?.defaultValue?.string) }
+
Input(type = InputType.Color) {
- style {
- width(100.percent)
- marginAll(0.px)
- }
+ classes("w-100")
+
value(
value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
@@ -154,8 +140,12 @@ public fun ColorValueChooser(
//else "#" + Color(value.string).getHexString()
} ?: "#000000"
)
- onChange {
- onValueChange(it.target.value.asValue())
+ onChange { event ->
+ innerValue = event.value
+ }
+ onInput { event ->
+ innerValue = event.value
+ onValueChange(event.value.asValue())
}
}
}
@@ -194,7 +184,7 @@ public fun RangeValueChooser(
value: Value?,
onValueChange: (Value?) -> Unit,
) {
- var innerValue by remember { mutableStateOf(value?.double) }
+ var innerValue by remember(value, descriptor) { mutableStateOf(value?.double) }
var rangeDisabled: Boolean by remember { mutableStateOf(state != EditorPropertyState.Defined) }
@@ -219,9 +209,8 @@ public fun RangeValueChooser(
}
Input(type = InputType.Range) {
- style {
- width(100.percent)
- }
+ classes("w-100")
+
if (rangeDisabled) disabled()
value(innerValue?.toString() ?: "")
onChange {
diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt
index 245953e0..f2e890b1 100644
--- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt
+++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeView.kt
@@ -14,7 +14,6 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
-import space.kscience.visionforge.Vision
import space.kscience.visionforge.compose.*
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.Solid
@@ -81,15 +80,6 @@ public fun ThreeView(
}
}
- val selectedVision: Vision? by derivedStateOf {
- selected?.let {
- when {
- it.isEmpty() -> solid
- else -> (solid as? SolidGroup)?.get(it)
- }
- }
- }
-
if (optionsSnapshot.controls.enabled) {
Row(
@@ -147,47 +137,57 @@ public fun ThreeView(
SimpleThreeView(solids.context, optionsSnapshot, solid, selected)
}
- selectedVision?.let { vision ->
- Card(
- attrs = {
- style {
- position(Position.Absolute)
- top(5.px)
- right(5.px)
- width(450.px)
- }
- },
- headerAttrs = {
- // border = true
- },
- header = {
- NameCrumbs(selected) { selected = it }
- },
- footer = {
- vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
- P {
- B { Text("Styles: ") }
- Text(styles.joinToString(separator = ", "))
- }
- }
+ key(selected) {
+ selected?.let {
+ when {
+ it.isEmpty() -> solid
+ else -> (solid as? SolidGroup)?.get(it)
}
- ) {
- PropertyEditor(
- scope = solids.context,
- meta = vision.properties.root(),
- getPropertyState = { name ->
- if (vision.properties.own?.get(name) != null) {
- EditorPropertyState.Defined
- } else if (vision.properties.root()[name] != null) {
- // TODO differentiate
- EditorPropertyState.Default()
- } else {
- EditorPropertyState.Undefined
+ }?.let { vision ->
+ Card(
+ attrs = {
+ style {
+ position(Position.Absolute)
+ top(5.px)
+ right(5.px)
+ width(450.px)
+ overflowY("auto")
}
},
- updates = vision.properties.changes,
- rootDescriptor = vision.descriptor
- )
+ headerAttrs = {
+ style {
+ alignItems(AlignItems.Center)
+ }
+ },
+ header = {
+ NameCrumbs(selected) { selected = it }
+ },
+ footer = {
+ vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
+ P {
+ B { Text("Styles: ") }
+ Text(styles.joinToString(separator = ", "))
+ }
+ }
+ }
+ ) {
+ PropertyEditor(
+ scope = solids.context,
+ rootMeta = vision.properties.root(),
+ getPropertyState = { name ->
+ if (vision.properties.own?.get(name) != null) {
+ EditorPropertyState.Defined
+ } else if (vision.properties.root()[name] != null) {
+ // TODO differentiate
+ EditorPropertyState.Default()
+ } else {
+ EditorPropertyState.Undefined
+ }
+ },
+ updates = vision.properties.changes,
+ rootDescriptor = vision.descriptor
+ )
+ }
}
}
}
@@ -204,7 +204,6 @@ public fun ThreeView(
paddingAll(4.px)
minWidth(400.px)
height(100.percent)
- overflowY("auto")
}
}
) {