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
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<dynamic>("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<dynamic>("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,

View File

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