Optimize UI
This commit is contained in:
parent
b5a1296070
commit
7871987df1
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,8 +23,10 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
||||
link(Name.EMPTY)
|
||||
}
|
||||
}) {
|
||||
A("#") {
|
||||
Text("\u2302")
|
||||
}
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
val tokens = ArrayList<NameToken>(name.length)
|
||||
@ -41,9 +40,11 @@ public fun NameCrumbs(name: Name?, link: (Name) -> Unit): Unit = Nav({
|
||||
link(fullName)
|
||||
}
|
||||
}) {
|
||||
A("#") {
|
||||
Text(token.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>,
|
||||
) {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 ->
|
||||
Button("Export", color = Info, styling = { Layout.width = Width.Full }) {
|
||||
val json = vision.encodeToString()
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
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")
|
||||
}
|
||||
fileSaver.saveAs(blob, "${options.canvasName}.json") as Unit
|
||||
}
|
||||
}
|
||||
Hr()
|
||||
PropertyEditor(
|
||||
scope = vision?.manager?.context ?: Global,
|
||||
properties = options.meta,
|
||||
|
@ -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({ classes("d-flex", "justify-content-center") }) {
|
||||
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)
|
||||
}
|
||||
classes("spinner-border")
|
||||
attr("role", "status")
|
||||
}) {
|
||||
Span({ classes("sr-only") }) { Text("Loading 3D vision") }
|
||||
Span({ classes("visually-hidden") }) {
|
||||
Text("Loading...")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,33 +124,17 @@ public fun ThreeView(
|
||||
else -> (solid as? SolidGroup)?.get(it)
|
||||
}
|
||||
}?.let { vision ->
|
||||
Card(
|
||||
attrs = {
|
||||
Card(attrs = {
|
||||
style {
|
||||
position(Position.Absolute)
|
||||
top(5.px)
|
||||
right(5.px)
|
||||
top(10.px)
|
||||
right(10.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 = ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user