Optimize UI

This commit is contained in:
Alexander Nozik 2023-12-28 10:20:02 +03:00
parent b5a1296070
commit 7871987df1
9 changed files with 85 additions and 130 deletions

View File

@ -3,12 +3,12 @@
package space.kscience.visionforge.gdml.demo package space.kscience.visionforge.gdml.demo
import androidx.compose.runtime.* import androidx.compose.runtime.*
import app.softwork.bootstrapcompose.Icon
import org.jetbrains.compose.web.ExperimentalComposeWebApi import org.jetbrains.compose.web.ExperimentalComposeWebApi
import org.jetbrains.compose.web.attributes.InputType import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.name import org.jetbrains.compose.web.attributes.name
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.Div 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.Input
import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.dom.Text
import org.w3c.files.FileList 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) Text(title)
Input(type = InputType.File, attrs = { Input(type = InputType.File, attrs = {
style { style {

View File

@ -5,7 +5,9 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import app.softwork.bootstrapcompose.Button import app.softwork.bootstrapcompose.Button
import app.softwork.bootstrapcompose.ButtonGroup import app.softwork.bootstrapcompose.ButtonGroup
import app.softwork.bootstrapcompose.Color.Secondary
import app.softwork.bootstrapcompose.Container import app.softwork.bootstrapcompose.Container
import app.softwork.bootstrapcompose.Layout.Width
import kotlinx.browser.window import kotlinx.browser.window
import kotlinx.coroutines.await import kotlinx.coroutines.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -64,7 +66,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
options = mmOptions, options = mmOptions,
sidebarTabs = { sidebarTabs = {
Tab("Events") { Tab("Events") {
ButtonGroup { ButtonGroup({ Layout.width = Width.Full }) {
Button("Next") { Button("Next") {
solids.context.launch { solids.context.launch {
val event = window.fetch( val event = window.fetch(
@ -83,7 +85,7 @@ fun MMApp(solids: Solids, model: Model, selected: Name? = null) {
model.displayEvent(event) model.displayEvent(event)
} }
} }
Button("Clear") { Button("Clear", color = Secondary) {
events.clear() events.clear()
model.reset() model.reset()
} }

View File

@ -1,10 +1,7 @@
package space.kscience.visionforge.compose package space.kscience.visionforge.compose
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import org.jetbrains.compose.web.dom.Li import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.dom.Nav
import org.jetbrains.compose.web.dom.Ol
import org.jetbrains.compose.web.dom.Text
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.length import space.kscience.dataforge.names.length
@ -17,7 +14,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",".1rem") property("--bs-breadcrumb-item-padding-x", ".1rem")
} }
}) { }) {
Li({ Li({
@ -26,7 +23,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
link(Name.EMPTY) link(Name.EMPTY)
} }
}) { }) {
Text("\u2302") A("#") {
Text("\u2302")
}
} }
if (name != null) { if (name != null) {
@ -41,7 +40,9 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
link(fullName) link(fullName)
} }
}) { }) {
Text(token.toString()) A("#") {
Text(token.toString())
}
} }
} }
} }

View File

@ -69,7 +69,7 @@ public class TabsBuilder {
@Composable @Composable
public fun Tab( public fun Tab(
key: String, key: String,
label: ContentBuilder<HTMLAnchorElement> = { Text(key) }, label: ContentBuilder<HTMLAnchorElement> = { A("#") { Text(key) } },
disabled: Boolean = false, disabled: Boolean = false,
content: ContentBuilder<HTMLDivElement>, content: ContentBuilder<HTMLDivElement>,
) { ) {

View File

@ -10,7 +10,7 @@ public object TreeStyles : StyleSheet(VisionForgeStyles) {
* Remove default bullets * Remove default bullets
*/ */
public val tree: String by style { public val tree: String by style {
paddingLeft(5.px) paddingLeft(10.px)
marginLeft(0.px) marginLeft(0.px)
listStyleType("none") listStyleType("none")
} }

View File

@ -3,16 +3,14 @@
package space.kscience.visionforge.compose package space.kscience.visionforge.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import kotlinx.uuid.UUID
import kotlinx.uuid.generateUUID
import org.jetbrains.compose.web.attributes.* import org.jetbrains.compose.web.attributes.*
import org.jetbrains.compose.web.dom.Input import org.jetbrains.compose.web.dom.*
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.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.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRestriction
import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType import space.kscience.visionforge.widgetType
@ -47,20 +45,31 @@ public fun BooleanValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
val uid = remember { "checkbox[${UUID.generateUUID().toString(false)}]" }
var innerValue by remember(value, descriptor) { var innerValue by remember(value, descriptor) {
mutableStateOf( mutableStateOf(
value?.boolean ?: descriptor?.defaultValue?.boolean value?.boolean ?: descriptor?.defaultValue?.boolean
) )
} }
Input(type = InputType.Checkbox) { Input(type = InputType.Checkbox) {
classes("w-100") classes("btn-check")
checked(innerValue ?: false) checked(innerValue ?: false)
autoComplete(AutoComplete.off)
id(uid)
onInput { event -> onInput { event ->
innerValue = event.value innerValue = event.value
onValueChange(event.value.asValue()) 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 @Composable
@ -70,7 +79,7 @@ public fun NumberValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, 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) { Input(type = InputType.Number) {
classes("w-100") classes("w-100")
@ -159,6 +168,7 @@ public fun MultiSelectChooser(
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
Select({ Select({
classes("w-100","form-select")
onChange { event -> onChange { event ->
val newSelected = event.target.selectedOptions.asList() val newSelected = event.target.selectedOptions.asList()
.map { (it as HTMLOptionElement).value.asValue() } .map { (it as HTMLOptionElement).value.asValue() }
@ -184,39 +194,18 @@ public fun RangeValueChooser(
value: Value?, value: Value?,
onValueChange: (Value?) -> Unit, onValueChange: (Value?) -> Unit,
) { ) {
var innerValue by remember(value, descriptor) { mutableStateOf(value?.double) } var innerValue by remember(value, descriptor) { mutableStateOf(value?.number ?: descriptor?.defaultValue?.number) }
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()
}
)
}
}
}
}
Input(type = InputType.Range) { Input(type = InputType.Range) {
classes("w-100") classes("w-100", "form-range")
if (rangeDisabled) disabled()
value(innerValue?.toString() ?: "") value(innerValue?.toString() ?: "")
onChange { onInput { event ->
val newValue = it.target.value innerValue = event.value
onValueChange(newValue.toDoubleOrNull()?.asValue()) }
innerValue = newValue.toDoubleOrNull() onChange { event ->
innerValue = event.value
onValueChange(innerValue?.asValue())
} }
descriptor?.attributes?.get("min").string?.let { descriptor?.attributes?.get("min").string?.let {
min(it) min(it)

View File

@ -59,6 +59,8 @@ public class CanvasSize : Scheme() {
} }
public class Canvas3DOptions : Scheme() { public class Canvas3DOptions : Scheme() {
public var canvasName: String by string("vision")
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
public var axes: AxesScheme by spec(AxesScheme) public var axes: AxesScheme by spec(AxesScheme)
public var camera: CameraScheme by spec(CameraScheme) public var camera: CameraScheme by spec(CameraScheme)

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.solid.three.compose package space.kscience.visionforge.solid.three.compose
import androidx.compose.runtime.Composable 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.Column
import app.softwork.bootstrapcompose.Layout.Height import app.softwork.bootstrapcompose.Layout.Height
import app.softwork.bootstrapcompose.Row import app.softwork.bootstrapcompose.Layout.Width
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.dom.Hr
import org.jetbrains.compose.web.dom.Button
import org.jetbrains.compose.web.dom.Text
import org.w3c.files.Blob import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag import org.w3c.files.BlobPropertyBag
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Global
@ -22,32 +22,16 @@ internal fun CanvasControls(
options: Canvas3DOptions, options: Canvas3DOptions,
) { ) {
Column { Column {
Row(attrs = { vision?.let { vision ->
style { Button("Export", color = Info, styling = { Layout.width = Width.Full }) {
border { val json = vision.encodeToString()
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();
val fileSaver = kotlinext.js.require<dynamic>("file-saver") val fileSaver = kotlinext.js.require<dynamic>("file-saver")
val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8")) val blob = Blob(arrayOf(json), BlobPropertyBag("text/json;charset=utf-8"))
fileSaver.saveAs(blob, "object.json") as Unit fileSaver.saveAs(blob, "${options.canvasName}.json") as Unit
}
}) {
Text("Export")
}
} }
} }
Hr()
PropertyEditor( PropertyEditor(
scope = vision?.manager?.context ?: Global, scope = vision?.manager?.context ?: Global,
properties = options.meta, properties = options.meta,

View File

@ -7,7 +7,6 @@ import app.softwork.bootstrapcompose.Layout.Height
import app.softwork.bootstrapcompose.Layout.Width import app.softwork.bootstrapcompose.Layout.Width
import app.softwork.bootstrapcompose.Row import app.softwork.bootstrapcompose.Row
import kotlinx.dom.clear import kotlinx.dom.clear
import org.jetbrains.compose.web.ExperimentalComposeWebApi
import org.jetbrains.compose.web.css.* import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -104,32 +103,13 @@ public fun ThreeView(
} }
) { ) {
if (solid == null) { if (solid == null) {
Div({ Div({ classes("d-flex", "justify-content-center") }) {
style { Div({
position(Position.Fixed) classes("spinner-border")
width(100.percent) attr("role", "status")
height(100.percent) }) {
zIndex(1000) Span({ classes("visually-hidden") }) {
top(40.percent) Text("Loading...")
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") }
} }
} }
} }
@ -144,33 +124,17 @@ public fun ThreeView(
else -> (solid as? SolidGroup)?.get(it) else -> (solid as? SolidGroup)?.get(it)
} }
}?.let { vision -> }?.let { vision ->
Card( Card(attrs = {
attrs = { style {
style { position(Position.Absolute)
position(Position.Absolute) top(10.px)
top(5.px) right(10.px)
right(5.px) width(450.px)
width(450.px) overflowY("auto")
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 = ", "))
}
}
} }
) { }) {
NameCrumbs(selected) { selected = it }
Hr()
PropertyEditor( PropertyEditor(
scope = solids.context, scope = solids.context,
rootMeta = vision.properties.root(), rootMeta = vision.properties.root(),
@ -187,6 +151,13 @@ public fun ThreeView(
updates = vision.properties.changes, updates = vision.properties.changes,
rootDescriptor = vision.descriptor 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 { } else {