From de2ef1dcc5cc2ece398ea2d428e6eaf9709691a1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 9 Aug 2020 14:41:24 +0300 Subject: [PATCH] UI tune-up --- build.gradle.kts | 2 +- .../vision/gdml/demo/GDMLAppComponent.kt | 7 +- .../mipt/npm/muon/monitor/MMAppComponent.kt | 2 +- playground/src/jsMain/kotlin/PlayGroundApp.kt | 2 +- .../vision/bootstrap/MetaViewerComponent.kt | 11 +- .../dataforge/vision/bootstrap/bootstrap.kt | 20 ++++ .../{configEditor.kt => ConfigEditor.kt} | 51 +++------ .../hep/dataforge/vision/react}/ObjectTree.kt | 75 ++++++------ .../hep/dataforge/vision/react/TreeStyles.kt | 83 +++++++++++--- .../dataforge/vision/react/valueChooser.kt | 108 ++++++++---------- 10 files changed, 199 insertions(+), 162 deletions(-) rename ui/react/src/main/kotlin/hep/dataforge/vision/react/{configEditor.kt => ConfigEditor.kt} (82%) rename ui/{bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap => react/src/main/kotlin/hep/dataforge/vision/react}/ObjectTree.kt (69%) diff --git a/build.gradle.kts b/build.gradle.kts index 56f0b451..f2da0ea9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ allprojects { } group = "hep.dataforge" - version = "0.1.5-dev" + version = "0.1.5-dev-2" } val githubProject by extra("visionforge") diff --git a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt index ee788190..c189b6b8 100644 --- a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vision/gdml/demo/GDMLAppComponent.kt @@ -7,10 +7,7 @@ import hep.dataforge.vision.Vision import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.bootstrap.* import hep.dataforge.vision.gdml.toVision -import hep.dataforge.vision.react.component -import hep.dataforge.vision.react.configEditor -import hep.dataforge.vision.react.flexColumn -import hep.dataforge.vision.react.state +import hep.dataforge.vision.react.* import hep.dataforge.vision.solid.Solid import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.specifications.Camera @@ -98,7 +95,7 @@ val GDMLApp = component { props -> } } //tree - card("Object tree", "overflow-auto") { + card("Object tree") { visual?.let { objectTree(it, selected, select) } 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 1291efb0..d08788c4 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 @@ -6,9 +6,9 @@ import hep.dataforge.names.NameToken import hep.dataforge.names.isEmpty import hep.dataforge.vision.Vision import hep.dataforge.vision.bootstrap.card -import hep.dataforge.vision.bootstrap.objectTree import hep.dataforge.vision.react.component import hep.dataforge.vision.react.configEditor +import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.react.state import hep.dataforge.vision.solid.specifications.Camera import hep.dataforge.vision.solid.specifications.Canvas3DOptions diff --git a/playground/src/jsMain/kotlin/PlayGroundApp.kt b/playground/src/jsMain/kotlin/PlayGroundApp.kt index c4377c05..3b8a04a4 100644 --- a/playground/src/jsMain/kotlin/PlayGroundApp.kt +++ b/playground/src/jsMain/kotlin/PlayGroundApp.kt @@ -2,8 +2,8 @@ import hep.dataforge.context.Global import hep.dataforge.js.Application import hep.dataforge.js.startApplication import hep.dataforge.names.Name -import hep.dataforge.vision.bootstrap.objectTree import hep.dataforge.vision.bootstrap.visualPropertyEditor +import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.solid.Point3D import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.box diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/MetaViewerComponent.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/MetaViewerComponent.kt index a8bd0f26..d4809611 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/MetaViewerComponent.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/MetaViewerComponent.kt @@ -7,11 +7,8 @@ import hep.dataforge.names.NameToken import kotlinx.html.classes import kotlinx.html.js.onClickFunction import org.w3c.dom.events.Event -import react.RBuilder -import react.RComponent -import react.RProps +import react.* import react.dom.* -import react.setState interface MetaViewerProps : RProps { var name: NameToken @@ -19,6 +16,12 @@ interface MetaViewerProps : RProps { var descriptor: NodeDescriptor? } + +interface TreeState : RState { + var expanded: Boolean +} + +@Deprecated("To be replaced by react functional component") class MetaViewerComponent : RComponent() { override fun TreeState.init() { diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt index a0561bd1..d624be23 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/bootstrap.kt @@ -2,12 +2,16 @@ package hep.dataforge.vision.bootstrap import hep.dataforge.names.Name import hep.dataforge.names.NameToken +import hep.dataforge.vision.Vision +import hep.dataforge.vision.react.ObjectTree import kotlinx.html.* import kotlinx.html.js.div import kotlinx.html.js.onClickFunction +import org.w3c.dom.Element import org.w3c.dom.HTMLElement import react.RBuilder import react.ReactElement +import react.child import react.dom.* inline fun TagConsumer.card(title: String, crossinline block: TagConsumer.() -> Unit) { @@ -205,4 +209,20 @@ inline fun RBuilder.gridRow( block: RDOMBuilder
.() -> Unit ): ReactElement { return div(joinStyles(classes, "row"), block) +} + +fun Element.renderObjectTree( + vision: Vision, + clickCallback: (Name) -> Unit = {} +) = render(this) { + card("Object tree") { + child(ObjectTree) { + attrs { + this.name = Name.EMPTY + this.obj = vision + this.selected = null + this.clickCallback = clickCallback + } + } + } } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/configEditor.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt similarity index 82% rename from ui/react/src/main/kotlin/hep/dataforge/vision/react/configEditor.kt rename to ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt index 7f99d087..9331f581 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/configEditor.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt @@ -6,13 +6,10 @@ import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.plus import hep.dataforge.values.Value -import kotlinx.css.* -import kotlinx.css.properties.TextDecoration import kotlinx.html.js.onClickFunction import org.w3c.dom.Element import org.w3c.dom.events.Event import react.* -import react.dom.div import react.dom.render import styled.* @@ -86,7 +83,10 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) { when (actualItem) { is MetaItem.NodeItem -> { - div { + styledDiv { + css { + +TreeStyles.treeLeaf + } styledSpan { css { +TreeStyles.treeCaret @@ -145,15 +145,14 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) { styledDiv { css { +TreeStyles.treeLeaf - justifyContent = JustifyContent.flexEnd +// justifyContent = JustifyContent.flexEnd } styledDiv { css { - flexGrow = 1.0 + +TreeStyles.treeLabel } styledSpan { css { - +TreeStyles.treeLabel if (item == null) { +TreeStyles.treeLabelInactive } @@ -162,6 +161,9 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) { } } styledDiv { + css { + +TreeStyles.resizeableInput + } valueChooser( props.name, actualItem, @@ -169,37 +171,20 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) { valueChanged ) } - styledDiv { + styledButton { css { - flexShrink = 1.0 + +TreeStyles.removeButton } - styledButton { - css { - backgroundColor = Color.white - borderStyle = BorderStyle.solid - borderRadius = 2.px - padding(1.px, 5.px) - marginLeft = 4.px - textAlign = TextAlign.center - textDecoration = TextDecoration.none - display = Display.inlineBlock - cursor = Cursor.pointer - disabled { - cursor = Cursor.auto - borderStyle = BorderStyle.dashed - color = Color.lightGray - } - } - +"\u00D7" - attrs { - if (item == null) { - disabled = true - } else { - onClickFunction = removeClick - } + +"\u00D7" + attrs { + if (item == null) { + disabled = true + } else { + onClickFunction = removeClick } } } + } } } diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/ObjectTree.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ObjectTree.kt similarity index 69% rename from ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/ObjectTree.kt rename to ui/react/src/main/kotlin/hep/dataforge/vision/react/ObjectTree.kt index d2ed503f..3b2112fb 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/ObjectTree.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/ObjectTree.kt @@ -1,4 +1,4 @@ -package hep.dataforge.vision.bootstrap +package hep.dataforge.vision.react import hep.dataforge.names.Name import hep.dataforge.names.plus @@ -6,14 +6,10 @@ import hep.dataforge.names.startsWith import hep.dataforge.vision.Vision import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.isEmpty -import hep.dataforge.vision.react.RFBuilder -import hep.dataforge.vision.react.component -import kotlinx.html.classes import kotlinx.html.js.onClickFunction -import org.w3c.dom.Element import org.w3c.dom.events.Event import react.* -import react.dom.* +import styled.* interface ObjectTreeProps : RProps { var name: Name @@ -22,24 +18,25 @@ interface ObjectTreeProps : RProps { var clickCallback: (Name) -> Unit } -interface TreeState : RState { - var expanded: Boolean -} - private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { - var expanded: Boolean by useState{ props.selected?.startsWith(props.name) ?: false } + var expanded: Boolean by useState { props.selected?.startsWith(props.name) ?: false } val onClick: (Event) -> Unit = { expanded = !expanded } fun RBuilder.treeLabel(text: String) { - button(classes = "btn btn-link align-middle tree-label p-0") { + styledButton { + css { + //classes = mutableListOf("btn", "btn-link", "align-middle", "text-truncate", "p-0") + +TreeStyles.treeLabel + +TreeStyles.linkButton + if (props.name == props.selected) { + +TreeStyles.treeLabelSelected + } + } +text attrs { - if (props.name == props.selected) { - classes += "tree-label-selected" - } onClickFunction = { props.clickCallback(props.name) } } } @@ -50,13 +47,19 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { //display as node if any child is visible if (obj is VisionGroup) { - div("d-block text-truncate") { + styledDiv { + css { + +TreeStyles.treeLeaf + } if (obj.children.any { !it.key.body.startsWith("@") }) { - span("tree-caret") { - attrs { + styledSpan { + css { + +TreeStyles.treeCaret if (expanded) { - classes += "tree-caret-down" + +TreeStyles.treeCaredDown } + } + attrs { onClickFunction = onClick } } @@ -64,12 +67,18 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { treeLabel(token) } if (expanded) { - ul("tree") { + styledUl { + css { + +TreeStyles.tree + } obj.children.entries .filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children - .sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true } + .sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true } // ignore empty groups .forEach { (childToken, child) -> - li("tree-item") { + styledLi { + css { + +TreeStyles.treeItem + } child(ObjectTree) { attrs { this.name = props.name + childToken @@ -83,8 +92,10 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { } } } else { - div("d-block text-truncate") { - span("tree-leaf") {} + styledDiv { + css { + +TreeStyles.treeLeaf + } treeLabel(token) } } @@ -94,22 +105,6 @@ val ObjectTree: FunctionalComponent = component { props -> objectTree(props) } -fun Element.renderObjectTree( - vision: Vision, - clickCallback: (Name) -> Unit = {} -) = render(this) { - card("Object tree") { - child(ObjectTree) { - attrs { - this.name = Name.EMPTY - this.obj = vision - this.selected = null - this.clickCallback = clickCallback - } - } - } -} - fun RBuilder.objectTree( vision: Vision, selected: Name? = null, diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt index d28a6ede..ead7d577 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/TreeStyles.kt @@ -1,8 +1,7 @@ package hep.dataforge.vision.react import kotlinx.css.* -import kotlinx.css.properties.deg -import kotlinx.css.properties.rotate +import kotlinx.css.properties.* import styled.StyleSheet object TreeStyles : StyleSheet("treeStyles", true) { @@ -30,22 +29,6 @@ object TreeStyles : StyleSheet("treeStyles", true) { } } - val treeItem by css { - alignItems = Align.center - paddingLeft = 10.px - borderLeftStyle = BorderStyle.dashed - borderLeftWidth = 1.px - borderLeftColor = Color.lightGray - } - - val treeLeaf by css { - display = Display.flex - flexDirection = FlexDirection.row - userSelect = UserSelect.none - alignItems = Align.center - } - - /** * Rotate the caret/arrow icon when clicked on (using JavaScript) */ @@ -59,8 +42,27 @@ object TreeStyles : StyleSheet("treeStyles", true) { } } + val treeItem by css { + alignItems = Align.center + paddingLeft = 10.px + borderLeftStyle = BorderStyle.dashed + borderLeftWidth = 1.px + borderLeftColor = Color.lightGray + borderBottomStyle = BorderStyle.dashed + borderBottomWidth = 1.px + borderBottomColor = Color.lightGray + } + + val treeLeaf by css { + display = Display.flex + flexDirection = FlexDirection.row + flexWrap = FlexWrap.nowrap + //alignItems = Align.center + } + val treeLabel by css { overflow = Overflow.hidden + flex(flexGrow = 1.0, flexShrink = 1.0) } val treeLabelInactive by css { @@ -71,4 +73,49 @@ object TreeStyles : StyleSheet("treeStyles", true) { backgroundColor = Color.lightBlue } + val linkButton by css { + backgroundColor = Color.white + border = "none" + padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt) + textAlign = TextAlign.left + fontFamily = "arial,sans-serif" + color = Color("#069") + cursor = Cursor.pointer + hover { + textDecoration(TextDecorationLine.underline) + } + } + + val removeButton by css { + backgroundColor = Color.white + borderStyle = BorderStyle.solid + borderRadius = 2.px + padding(1.px, 5.px) + marginLeft = 4.px + textAlign = TextAlign.center + textDecoration = TextDecoration.none + display = Display.inlineBlock + flexShrink = 1.0 + cursor = Cursor.pointer + disabled { + cursor = Cursor.auto + borderStyle = BorderStyle.dashed + color = Color.lightGray + } + } + + val resizeableInput by css { + overflow = Overflow.hidden + maxWidth = 120.pt + flex(flexGrow = 2.0, flexShrink = 2.0, flexBasis = 60.pt) + input { + textAlign = TextAlign.right + width = 100.pct + } + select{ + textAlign = TextAlign.right + width = 100.pct + } + } + } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt index e7562131..cf51b5aa 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/valueChooser.kt @@ -6,8 +6,6 @@ import hep.dataforge.names.Name import hep.dataforge.values.* import hep.dataforge.vision.Colors import hep.dataforge.vision.widgetType -import kotlinx.css.Align -import kotlinx.css.alignSelf import kotlinx.html.InputType import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onKeyDownFunction @@ -17,11 +15,9 @@ import org.w3c.dom.HTMLSelectElement import org.w3c.dom.events.Event import react.* import react.dom.defaultValue -import react.dom.input import react.dom.option -import react.dom.select -import styled.css -import styled.styledDiv +import styled.styledInput +import styled.styledSelect interface ValueChooserProps : RProps { var item: MetaItem<*>? @@ -75,7 +71,7 @@ class ValueChooserComponent(props: ValueChooserProps) : RComponent stringInput() + descriptor?.widgetType == "color" -> styledInput(type = InputType.color) { + ref = element + attrs { + this.defaultValue = props.item?.value?.let { value -> + if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) + else value.string + } ?: "#000000" + onChangeFunction = commit + } } - val descriptor = props.descriptor - val type = descriptor?.type?.firstOrNull() - when { - state.rawInput == true -> stringInput() - descriptor?.widgetType == "color" -> input(type = InputType.color) { + type == ValueType.BOOLEAN -> { + styledInput(type = InputType.checkBox) { ref = element attrs { - this.defaultValue = props.item?.value?.let { value -> - if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) - else value.string - } ?: "#000000" + defaultChecked = props.item?.boolean ?: false onChangeFunction = commit } } - type == ValueType.BOOLEAN -> { - input(type = InputType.checkBox) { - ref = element - attrs { - defaultChecked = props.item?.boolean ?: false - onChangeFunction = commit - } - } - } - type == ValueType.NUMBER -> input(type = InputType.number) { - ref = element - attrs { - descriptor.attributes["step"].string?.let { - step = it - } - descriptor.attributes["min"].string?.let { - min = it - } - descriptor.attributes["max"].string?.let { - max = it - } - defaultValue = props.item?.string ?: "" - onKeyDownFunction = keyDown - } - } - descriptor?.allowedValues?.isNotEmpty() ?: false -> select { - descriptor!!.allowedValues.forEach { - option { - +it.string - } - } - ref = element - attrs { - this.value = props.item?.string ?: "" - multiple = false - onChangeFunction = commit - } - } - else -> stringInput() } + type == ValueType.NUMBER -> styledInput(type = InputType.number) { + ref = element + attrs { + descriptor.attributes["step"].string?.let { + step = it + } + descriptor.attributes["min"].string?.let { + min = it + } + descriptor.attributes["max"].string?.let { + max = it + } + defaultValue = props.item?.string ?: "" + onKeyDownFunction = keyDown + } + } + descriptor?.allowedValues?.isNotEmpty() ?: false -> styledSelect { + descriptor!!.allowedValues.forEach { + option { + +it.string + } + } + ref = element + attrs { + this.value = props.item?.string ?: "" + multiple = false + onChangeFunction = commit + } + } + else -> stringInput() } } - } internal fun RBuilder.valueChooser(