From 7871987df1abfc115ea424878735d4d6538bd10b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 28 Dec 2023 10:20:02 +0300 Subject: [PATCH] Optimize UI --- .../visionforge/gdml/demo/FileDrop.kt | 4 +- .../mipt/npm/muon/monitor/MMAppComponent.kt | 6 +- .../visionforge/compose/NameCrumbs.kt | 15 ++-- .../kscience/visionforge/compose/Tabs.kt | 2 +- .../visionforge/compose/TreeStyles.kt | 2 +- .../visionforge/compose/valueChooser.kt | 61 ++++++------- .../solid/specifications/Canvas3DOptions.kt | 2 + .../solid/three/compose/ThreeControls.kt | 38 +++------ .../solid/three/compose/ThreeView.kt | 85 +++++++------------ 9 files changed, 85 insertions(+), 130 deletions(-) diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/FileDrop.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/FileDrop.kt index 010f782b..d26ea42d 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/FileDrop.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/FileDrop.kt @@ -3,12 +3,12 @@ package space.kscience.visionforge.gdml.demo import androidx.compose.runtime.* +import app.softwork.bootstrapcompose.Icon import org.jetbrains.compose.web.ExperimentalComposeWebApi import org.jetbrains.compose.web.attributes.InputType import org.jetbrains.compose.web.attributes.name import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.Div -import org.jetbrains.compose.web.dom.I import org.jetbrains.compose.web.dom.Input import org.jetbrains.compose.web.dom.Text import org.w3c.files.FileList @@ -70,7 +70,7 @@ fun FileDrop( } }) { - I({ classes("bi", "bi-cloud-upload", "dropzone-icon") }) + Icon("cloud-upload"){ classes("dropzone-icon") } Text(title) Input(type = InputType.File, attrs = { style { diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt index 13d92075..09f3a46d 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt @@ -5,7 +5,9 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import app.softwork.bootstrapcompose.Button import app.softwork.bootstrapcompose.ButtonGroup +import app.softwork.bootstrapcompose.Color.Secondary import app.softwork.bootstrapcompose.Container +import app.softwork.bootstrapcompose.Layout.Width import kotlinx.browser.window import kotlinx.coroutines.await import kotlinx.coroutines.launch @@ -64,7 +66,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) { options = mmOptions, sidebarTabs = { Tab("Events") { - ButtonGroup { + ButtonGroup({ Layout.width = Width.Full }) { Button("Next") { solids.context.launch { val event = window.fetch( @@ -83,7 +85,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) { model.displayEvent(event) } } - Button("Clear") { + Button("Clear", color = Secondary) { events.clear() model.reset() } 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 2740b1e5..12d78210 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 @@ -1,10 +1,7 @@ package space.kscience.visionforge.compose import androidx.compose.runtime.Composable -import org.jetbrains.compose.web.dom.Li -import org.jetbrains.compose.web.dom.Nav -import org.jetbrains.compose.web.dom.Ol -import org.jetbrains.compose.web.dom.Text +import org.jetbrains.compose.web.dom.* import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.length @@ -17,7 +14,7 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({ classes("breadcrumb") style { property("--bs-breadcrumb-divider", "'.'") - property("--bs-breadcrumb-item-padding-x",".1rem") + property("--bs-breadcrumb-item-padding-x", ".1rem") } }) { Li({ @@ -26,7 +23,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({ link(Name.EMPTY) } }) { - Text("\u2302") + A("#") { + Text("\u2302") + } } if (name != null) { @@ -41,7 +40,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({ link(fullName) } }) { - Text(token.toString()) + A("#") { + Text(token.toString()) + } } } } 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 6073cdda..3e0db26a 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 @@ -69,7 +69,7 @@ public class TabsBuilder { @Composable public fun Tab( key: String, - label: ContentBuilder = { Text(key) }, + label: ContentBuilder = { A("#") { Text(key) } }, disabled: Boolean = false, content: ContentBuilder, ) { diff --git a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt index c97bdae2..95a58aaf 100644 --- a/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt +++ b/visionforge-compose-html/src/jsMain/kotlin/space/kscience/visionforge/compose/TreeStyles.kt @@ -10,7 +10,7 @@ public object TreeStyles : StyleSheet(VisionForgeStyles) { * Remove default bullets */ public val tree: String by style { - paddingLeft(5.px) + paddingLeft(10.px) marginLeft(0.px) listStyleType("none") } 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 f0f2451c..9b9a9905 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 @@ -3,16 +3,14 @@ package space.kscience.visionforge.compose import androidx.compose.runtime.* +import kotlinx.uuid.UUID +import kotlinx.uuid.generateUUID import org.jetbrains.compose.web.attributes.* -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.jetbrains.compose.web.dom.* import org.w3c.dom.HTMLOptionElement import org.w3c.dom.asList import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.ValueRestriction import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.visionforge.Colors import space.kscience.visionforge.widgetType @@ -47,20 +45,31 @@ public fun BooleanValueChooser( value: Value?, onValueChange: (Value?) -> Unit, ) { + val uid = remember { "checkbox[${UUID.generateUUID().toString(false)}]" } var innerValue by remember(value, descriptor) { mutableStateOf( value?.boolean ?: descriptor?.defaultValue?.boolean ) } + Input(type = InputType.Checkbox) { - classes("w-100") + classes("btn-check") checked(innerValue ?: false) + autoComplete(AutoComplete.off) + id(uid) onInput { event -> innerValue = event.value onValueChange(event.value.asValue()) } } + Label(uid, attrs = { classes("btn", "btn-sm", "btn-outline-secondary", "w-100") }) { + if (innerValue == true) { + Text("On") + } else { + Text("Off") + } + } } @Composable @@ -70,7 +79,7 @@ public fun NumberValueChooser( value: Value?, onValueChange: (Value?) -> Unit, ) { - var innerValue by remember(value, descriptor) { mutableStateOf(value?.number) } + var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) } Input(type = InputType.Number) { classes("w-100") @@ -159,6 +168,7 @@ public fun MultiSelectChooser( onValueChange: (Value?) -> Unit, ) { Select({ + classes("w-100","form-select") onChange { event -> val newSelected = event.target.selectedOptions.asList() .map { (it as HTMLOptionElement).value.asValue() } @@ -184,39 +194,18 @@ public fun RangeValueChooser( value: Value?, onValueChange: (Value?) -> Unit, ) { - var innerValue by remember(value, descriptor) { mutableStateOf(value?.double) } - var rangeDisabled: Boolean by remember { mutableStateOf(state != EditorPropertyState.Defined) } - - - FlexRow { - if (descriptor?.valueRestriction != ValueRestriction.REQUIRED) { - Input(type = InputType.Checkbox) { - if (!rangeDisabled) defaultChecked() - - onChange { - val checkBoxValue = it.target.checked - rangeDisabled = !checkBoxValue - onValueChange( - if (!checkBoxValue) { - null - } else { - innerValue?.asValue() - } - ) - } - } - } - } + var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) } Input(type = InputType.Range) { - classes("w-100") + classes("w-100", "form-range") - if (rangeDisabled) disabled() value(innerValue?.toString() ?: "") - onChange { - val newValue = it.target.value - onValueChange(newValue.toDoubleOrNull()?.asValue()) - innerValue = newValue.toDoubleOrNull() + onInput { event -> + innerValue = event.value + } + onChange { event -> + innerValue = event.value + onValueChange(innerValue?.asValue()) } descriptor?.attributes?.get("min").string?.let { min(it) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index 11f179ce..8a081d63 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -59,6 +59,8 @@ public class CanvasSize : Scheme() { } public class Canvas3DOptions : Scheme() { + public var canvasName: String by string("vision") + @Suppress("DEPRECATION") public var axes: AxesScheme by spec(AxesScheme) public var camera: CameraScheme by spec(CameraScheme) diff --git a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt index e37cfd6b..2e03ec6a 100644 --- a/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt +++ b/visionforge-threejs/src/jsMain/kotlin/space/kscience/visionforge/solid/three/compose/ThreeControls.kt @@ -1,12 +1,12 @@ package space.kscience.visionforge.solid.three.compose import androidx.compose.runtime.Composable +import app.softwork.bootstrapcompose.Button +import app.softwork.bootstrapcompose.Color.Info import app.softwork.bootstrapcompose.Column import app.softwork.bootstrapcompose.Layout.Height -import app.softwork.bootstrapcompose.Row -import org.jetbrains.compose.web.css.* -import org.jetbrains.compose.web.dom.Button -import org.jetbrains.compose.web.dom.Text +import app.softwork.bootstrapcompose.Layout.Width +import org.jetbrains.compose.web.dom.Hr import org.w3c.files.Blob import org.w3c.files.BlobPropertyBag import space.kscience.dataforge.context.Global @@ -22,32 +22,16 @@ internal fun CanvasControls( options: Canvas3DOptions, ) { Column { - Row(attrs = { - style { - border { - width(1.px) - style(LineStyle.Solid) - color(Color("blue")) - } - padding(4.px) - } - }) { - vision?.let { vision -> - Button({ - onClick { event -> - val json = vision.encodeToString() - event.stopPropagation(); - event.preventDefault(); + vision?.let { vision -> + Button("Export", color = Info, styling = { Layout.width = Width.Full }) { + val json = vision.encodeToString() - val fileSaver = kotlinext.js.require("file-saver") - val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8")) - fileSaver.saveAs(blob, "object.json") as Unit - } - }) { - Text("Export") - } + val fileSaver = kotlinext.js.require("file-saver") + val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8")) + fileSaver.saveAs(blob, "${options.canvasName}.json") as Unit } } + Hr() PropertyEditor( scope = vision?.manager?.context ?: Global, properties = options.meta, 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 f2e890b1..1c69ddb9 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 @@ -7,7 +7,6 @@ import app.softwork.bootstrapcompose.Layout.Height import app.softwork.bootstrapcompose.Layout.Width import app.softwork.bootstrapcompose.Row import kotlinx.dom.clear -import org.jetbrains.compose.web.ExperimentalComposeWebApi import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.* import space.kscience.dataforge.context.Context @@ -104,32 +103,13 @@ public fun ThreeView( } ) { if (solid == null) { - Div({ - style { - position(Position.Fixed) - width(100.percent) - height(100.percent) - zIndex(1000) - top(40.percent) - left(0.px) - opacity(0.5) - - @OptIn(ExperimentalComposeWebApi::class) filter { - opacity(50.percent) - } - } - }) { - Div({ classes("d-flex", " justify-content-center") }) { - Div({ - classes("spinner-grow", "text-primary") - style { - width(3.cssRem) - height(3.cssRem) - zIndex(20) - } - attr("role", "status") - }) { - Span({ classes("sr-only") }) { Text("Loading 3D vision") } + Div({ classes("d-flex", "justify-content-center") }) { + Div({ + classes("spinner-border") + attr("role", "status") + }) { + Span({ classes("visually-hidden") }) { + Text("Loading...") } } } @@ -144,33 +124,17 @@ public fun ThreeView( else -> (solid as? SolidGroup)?.get(it) } }?.let { vision -> - Card( - attrs = { - style { - position(Position.Absolute) - top(5.px) - right(5.px) - width(450.px) - overflowY("auto") - } - }, - 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 = ", ")) - } - } + Card(attrs = { + style { + position(Position.Absolute) + top(10.px) + right(10.px) + width(450.px) + overflowY("auto") } - ) { + }) { + NameCrumbs(selected) { selected = it } + Hr() PropertyEditor( scope = solids.context, rootMeta = vision.properties.root(), @@ -187,6 +151,13 @@ public fun ThreeView( updates = vision.properties.changes, rootDescriptor = vision.descriptor ) + vision.styles.takeIf { it.isNotEmpty() }?.let { styles -> + Hr() + P { + B { Text("Styles: ") } + Text(styles.joinToString(separator = ", ")) + } + } } } } @@ -207,7 +178,13 @@ public fun ThreeView( } } ) { - ThreeControls(solid, optionsSnapshot, selected, onSelect = { selected = it }, tabBuilder = sidebarTabs) + ThreeControls( + solid, + optionsSnapshot, + selected, + onSelect = { selected = it }, + tabBuilder = sidebarTabs + ) } } } else {