working muon monitor

This commit is contained in:
Alexander Nozik 2023-12-27 21:16:13 +03:00
parent 3f144a5dbd
commit b5a1296070
6 changed files with 113 additions and 137 deletions

View File

@ -17,7 +17,7 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
classes("breadcrumb") classes("breadcrumb")
style { style {
property("--bs-breadcrumb-divider", "'.'") property("--bs-breadcrumb-divider", "'.'")
property("--bs-breadcrumb-item-padding-x",".2rem") property("--bs-breadcrumb-item-padding-x",".1rem")
} }
}) { }) {
Li({ Li({

View File

@ -1,16 +1,16 @@
package space.kscience.visionforge.compose package space.kscience.visionforge.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.CloseButton
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch 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.alignItems import org.jetbrains.compose.web.css.alignItems
import org.jetbrains.compose.web.css.px import org.jetbrains.compose.web.css.px
import org.jetbrains.compose.web.css.width 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.Div
import org.jetbrains.compose.web.dom.Span import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.dom.Text
@ -35,13 +35,13 @@ public sealed class EditorPropertyState {
} }
/** /**
* @param meta Root config object - always non-null * @param rootMeta Root config object - always non-null
* @param rootDescriptor Full path to the displayed node in [meta]. Could be empty * @param rootDescriptor Full path to the displayed node in [rootMeta]. Could be empty
*/ */
@Composable @Composable
public fun PropertyEditor( public fun PropertyEditor(
scope: CoroutineScope, scope: CoroutineScope,
meta: MutableMeta, rootMeta: MutableMeta,
getPropertyState: (Name) -> EditorPropertyState, getPropertyState: (Name) -> EditorPropertyState,
updates: Flow<Name>, updates: Flow<Name>,
name: Name = Name.EMPTY, name: Name = Name.EMPTY,
@ -50,11 +50,11 @@ public fun PropertyEditor(
) { ) {
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) } var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
val descriptor: MetaDescriptor? = remember(rootDescriptor, name) { rootDescriptor?.get(name) } 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)) } var editorPropertyState: EditorPropertyState by remember { mutableStateOf(getPropertyState(name)) }
val keys = remember(descriptor) { val keys by derivedStateOf {
buildSet { buildSet {
descriptor?.children?.filterNot { descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden it.key.startsWith("@") || it.value.hidden
@ -68,11 +68,11 @@ public fun PropertyEditor(
val token = name.lastOrNull()?.toString() ?: "Properties" val token = name.lastOrNull()?.toString() ?: "Properties"
fun update() { fun update() {
property = meta.getOrCreate(name) property = rootMeta.getOrCreate(name)
editorPropertyState = getPropertyState(name) editorPropertyState = getPropertyState(name)
} }
LaunchedEffect(meta) { LaunchedEffect(rootMeta) {
updates.collect { updatedName -> updates.collect { updatedName ->
if (updatedName == name) { if (updatedName == name) {
update() update()
@ -116,18 +116,9 @@ public fun PropertyEditor(
} }
} }
Button({ CloseButton(editorPropertyState != EditorPropertyState.Defined){
classes(TreeStyles.propertyEditorButton) rootMeta.remove(name)
if (editorPropertyState != EditorPropertyState.Defined) { update()
disabled()
} else {
onClick {
meta.remove(name)
update()
}
}
}) {
Text("\u00D7")
} }
} }
} }
@ -139,7 +130,7 @@ public fun PropertyEditor(
Div({ Div({
classes(TreeStyles.treeItem) 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( PropertyEditor(
scope = scope, scope = scope,
meta = properties, rootMeta = properties,
getPropertyState = { name -> getPropertyState = { name ->
if (properties[name] != null) { if (properties[name] != null) {
EditorPropertyState.Defined EditorPropertyState.Defined
@ -172,9 +163,7 @@ public fun PropertyEditor(
} }
} }
invokeOnClose { awaitClose { properties.removeListener(scope) }
properties.removeListener(scope)
}
}, },
name = Name.EMPTY, name = Name.EMPTY,
rootDescriptor = descriptor, rootDescriptor = descriptor,

View File

@ -4,6 +4,7 @@ import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Card import app.softwork.bootstrapcompose.Card
import app.softwork.bootstrapcompose.NavbarLink import app.softwork.bootstrapcompose.NavbarLink
import app.softwork.bootstrapcompose.Styling import app.softwork.bootstrapcompose.Styling
import org.jetbrains.compose.web.css.overflowY
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLAnchorElement import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLDivElement
@ -51,6 +52,11 @@ public fun Tabs(
} }
} }
} }
},
bodyAttrs = {
style {
overflowY("auto")
}
} }
) { ) {
activeTab?.content?.invoke(this) activeTab?.content?.invoke(this)

View File

@ -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.color import org.jetbrains.compose.web.css.color
import org.jetbrains.compose.web.css.cursor 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.Div
import org.jetbrains.compose.web.dom.Span import org.jetbrains.compose.web.dom.Span
import org.jetbrains.compose.web.dom.Text 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.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence import space.kscience.visionforge.asSequence
import space.kscience.visionforge.compose.TreeStyles.hover
import space.kscience.visionforge.compose.TreeStyles.invoke
import space.kscience.visionforge.isEmpty import space.kscience.visionforge.isEmpty
@ -35,10 +32,6 @@ private fun TreeLabel(
style { style {
color(Color("#069")) color(Color("#069"))
cursor("pointer") cursor("pointer")
hover.invoke {
textDecorationLine("underline")
}
} }
onClick { clickCallback(name) } onClick { clickCallback(name) }
}) { }) {

View File

@ -4,14 +4,10 @@ package space.kscience.visionforge.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import org.jetbrains.compose.web.attributes.* 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.Input
import org.jetbrains.compose.web.dom.Option import org.jetbrains.compose.web.dom.Option
import org.jetbrains.compose.web.dom.Select import org.jetbrains.compose.web.dom.Select
import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLOptionElement import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.asList import org.w3c.dom.asList
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
@ -29,20 +25,16 @@ public fun StringValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var stringValue by remember { mutableStateOf(value?.string ?: "") } var stringValue by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
Input(type = InputType.Text) { Input(type = InputType.Text) {
style { classes("w-100")
width(100.percent)
}
value(stringValue) value(stringValue)
onKeyDown { event -> onChange { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { stringValue = event.value
stringValue = (event.target as HTMLInputElement).value
onValueChange(stringValue.asValue())
}
} }
onChange { onInput { event ->
stringValue = it.target.value stringValue = event.value
onValueChange(event.value.asValue())
} }
} }
} }
@ -55,16 +47,18 @@ public fun BooleanValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var innerValue by remember(value, descriptor) {
mutableStateOf(
value?.boolean ?: descriptor?.defaultValue?.boolean
)
}
Input(type = InputType.Checkbox) { Input(type = InputType.Checkbox) {
style { classes("w-100")
width(100.percent) checked(innerValue ?: false)
}
//this.attributes["indeterminate"] = (props.item == null).toString()
checked(value?.boolean ?: false)
onChange { onInput { event ->
val newValue = it.target.checked innerValue = event.value
onValueChange(newValue.asValue()) onValueChange(event.value.asValue())
} }
} }
} }
@ -76,25 +70,18 @@ public fun NumberValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var innerValue by remember { mutableStateOf(value?.string ?: "") } var innerValue by remember(value, descriptor) { mutableStateOf(value?.number) }
Input(type = InputType.Number) { Input(type = InputType.Number) {
style { classes("w-100")
width(100.percent)
value(innerValue ?: descriptor?.defaultValue?.number ?: 0.0)
onChange { event ->
innerValue = event.value
} }
value(innerValue) onInput { event ->
onKeyDown { event -> innerValue = event.value
if (event.type == "keydown" && event.asDynamic().key == "Enter") { onValueChange(event.value?.asValue())
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
} }
descriptor?.attributes?.get("step").number?.let { descriptor?.attributes?.get("step").number?.let {
step(it) step(it)
@ -116,11 +103,10 @@ public fun ComboValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var selected by remember { mutableStateOf(value?.string ?: "") } var selected by remember(value, descriptor) { mutableStateOf(value?.string ?: "") }
Select({ Select({
style { classes("w-100")
width(100.percent)
}
onChange { onChange {
selected = it.target.value selected = it.target.value
onValueChange(selected.asValue()) onValueChange(selected.asValue())
@ -142,11 +128,11 @@ public fun ColorValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var innerValue by remember { mutableStateOf<String?>(value?.string ?: descriptor?.defaultValue?.string) }
Input(type = InputType.Color) { Input(type = InputType.Color) {
style { classes("w-100")
width(100.percent)
marginAll(0.px)
}
value( value(
value?.let { value -> value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
@ -154,8 +140,12 @@ public fun ColorValueChooser(
//else "#" + Color(value.string).getHexString() //else "#" + Color(value.string).getHexString()
} ?: "#000000" } ?: "#000000"
) )
onChange { onChange { event ->
onValueChange(it.target.value.asValue()) innerValue = event.value
}
onInput { event ->
innerValue = event.value
onValueChange(event.value.asValue())
} }
} }
} }
@ -194,7 +184,7 @@ public fun RangeValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, 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) } var rangeDisabled: Boolean by remember { mutableStateOf(state != EditorPropertyState.Defined) }
@ -219,9 +209,8 @@ public fun RangeValueChooser(
} }
Input(type = InputType.Range) { Input(type = InputType.Range) {
style { classes("w-100")
width(100.percent)
}
if (rangeDisabled) disabled() if (rangeDisabled) disabled()
value(innerValue?.toString() ?: "") value(innerValue?.toString() ?: "")
onChange { onChange {

View File

@ -14,7 +14,6 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request import space.kscience.dataforge.context.request
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.compose.* import space.kscience.visionforge.compose.*
import space.kscience.visionforge.root import space.kscience.visionforge.root
import space.kscience.visionforge.solid.Solid 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) { if (optionsSnapshot.controls.enabled) {
Row( Row(
@ -147,47 +137,57 @@ public fun ThreeView(
SimpleThreeView(solids.context, optionsSnapshot, solid, selected) SimpleThreeView(solids.context, optionsSnapshot, solid, selected)
} }
selectedVision?.let { vision -> key(selected) {
Card( selected?.let {
attrs = { when {
style { it.isEmpty() -> solid
position(Position.Absolute) else -> (solid as? SolidGroup)?.get(it)
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 = ", "))
}
}
} }
) { }?.let { vision ->
PropertyEditor( Card(
scope = solids.context, attrs = {
meta = vision.properties.root(), style {
getPropertyState = { name -> position(Position.Absolute)
if (vision.properties.own?.get(name) != null) { top(5.px)
EditorPropertyState.Defined right(5.px)
} else if (vision.properties.root()[name] != null) { width(450.px)
// TODO differentiate overflowY("auto")
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
} }
}, },
updates = vision.properties.changes, headerAttrs = {
rootDescriptor = vision.descriptor 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) paddingAll(4.px)
minWidth(400.px) minWidth(400.px)
height(100.percent) height(100.percent)
overflowY("auto")
} }
} }
) { ) {