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")
style {
property("--bs-breadcrumb-divider", "'.'")
property("--bs-breadcrumb-item-padding-x",".2rem")
property("--bs-breadcrumb-item-padding-x",".1rem")
}
}) {
Li({

View File

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

View File

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

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.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) }
}) {

View File

@ -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<String?>(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 {

View File

@ -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")
}
}
) {