Fix design issues with labels/breadcrumbs

This commit is contained in:
Alexander Nozik 2020-04-18 16:06:00 +03:00
parent ea73650b50
commit ac86832616
12 changed files with 155 additions and 132 deletions

View File

@ -86,6 +86,10 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
} }
} }
private val removeValue: (Event) -> Unit = {
props.root.remove(props.name)
}
//TODO replace by separate components //TODO replace by separate components
private fun RBuilder.valueChooser(value: Value, descriptor: ValueDescriptor?) { private fun RBuilder.valueChooser(value: Value, descriptor: ValueDescriptor?) {
val type = descriptor?.type?.firstOrNull() val type = descriptor?.type?.firstOrNull()
@ -98,6 +102,15 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
} }
type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") { type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") {
attrs { attrs {
descriptor.attributes["step"].string?.let {
step = it
}
descriptor.attributes["min"].string?.let {
min = it
}
descriptor.attributes["max"].string?.let {
max = it
}
defaultValue = value.string defaultValue = value.string
onChangeFunction = onValueChange onChangeFunction = onValueChange
} }
@ -195,9 +208,22 @@ class ConfigEditorComponent : RComponent<ConfigEditorProps, TreeState>() {
} }
} }
} }
if (item != null) {
div("col-auto") {
button(classes = "btn btn-default") {
span("glyphicon glyphicon-remove") {
attrs {
attributes["aria-hidden"] = "true"
}
}
attrs {
onClickFunction = removeValue
}
}
}
}
div("col") { div("col") {
valueChooser(actualItem.value, descriptorItem as? ValueDescriptor) valueChooser(actualItem.value, descriptorItem as? ValueDescriptor)
//+actualItem.value.toString()
} }
} }
} }
@ -220,6 +246,7 @@ fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, def
} }
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) { fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
div {
child(ConfigEditorComponent::class) { child(ConfigEditorComponent::class) {
attrs { attrs {
root = config root = config
@ -229,14 +256,8 @@ fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, de
} }
} }
} }
}
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) { fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
child(ConfigEditorComponent::class) { configEditor(obj.config, descriptor ?: obj.descriptor, default)
attrs {
root = obj.config
name = Name.EMPTY
this.descriptor = descriptor
this.default = default
}
}
} }

View File

@ -39,7 +39,7 @@ class ObjectTree : RComponent<ObjectTreeProps, TreeState>() {
} }
private fun RBuilder.treeLabel(text: String) { private fun RBuilder.treeLabel(text: String) {
a("#", classes = "tree-label") { button(classes = "btn btn-link tree-label p-0") {
+text +text
attrs { attrs {
if (props.name == props.selected) { if (props.name == props.selected) {

View File

@ -2,7 +2,6 @@ package hep.dataforge.vis.editor
import hep.dataforge.js.card import hep.dataforge.js.card
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
@ -19,10 +18,9 @@ fun RBuilder.visualPropertyEditor(
path: Name, path: Name,
item: VisualObject, item: VisualObject,
descriptor: NodeDescriptor? = item.descriptor, descriptor: NodeDescriptor? = item.descriptor,
title: String = "Properties", default: Meta? = null
default: MetaBuilder.() -> Unit = {}
) { ) {
card(title) { card("Properties") {
if (!path.isEmpty()) { if (!path.isEmpty()) {
nav { nav {
attrs { attrs {
@ -37,7 +35,7 @@ fun RBuilder.visualPropertyEditor(
} }
} }
} }
configEditor(item, descriptor, Meta(default)) configEditor(item, descriptor, default)
} }
} }
@ -45,8 +43,7 @@ fun Element.visualPropertyEditor(
path: Name, path: Name,
item: VisualObject, item: VisualObject,
descriptor: NodeDescriptor? = item.descriptor, descriptor: NodeDescriptor? = item.descriptor,
title: String = "Properties", default: Meta? = null
default: MetaBuilder.() -> Unit = {}
) = render(this) { ) = render(this) {
visualPropertyEditor(path, item, descriptor, title, default) this.visualPropertyEditor(path, item, descriptor, default)
} }

View File

@ -0,0 +1,11 @@
package hep.dataforge.vis.editor
//val TextValueChooser = functionalComponent<ConfigEditorProps> {
//
// input(type = InputType.number, classes = "float-right") {
// attrs {
// defaultValue = value.string
// onChangeFunction = onValueChange
// }
// }
//}

View File

@ -42,3 +42,7 @@ ul, .tree {
.tree-label-selected{ .tree-label-selected{
background-color: lightblue; background-color: lightblue;
} }
.no-padding{
padding: 0;
}

View File

@ -56,6 +56,13 @@ class Material3D : Scheme() {
defineValue(OPACITY_KEY) { defineValue(OPACITY_KEY) {
type(ValueType.NUMBER) type(ValueType.NUMBER)
default(1.0) default(1.0)
configure {
"attributes" to {
this["min"] = 0.0
this["max"] = 1.0
this["step"] = 0.1
}
}
} }
defineValue(WIREFRAME_KEY) { defineValue(WIREFRAME_KEY) {
type(ValueType.BOOLEAN) type(ValueType.BOOLEAN)

View File

@ -168,11 +168,16 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
} }
} }
override fun render(obj: VisualObject3D, meta: Meta) { fun clear(){
//clear old root
scene.children.find { it.name == "@root" }?.let { scene.children.find { it.name == "@root" }?.let {
scene.remove(it) scene.remove(it)
} }
}
override fun render(obj: VisualObject3D, meta: Meta) {
//clear old root
clear()
val object3D = three.buildObject3D(obj) val object3D = three.buildObject3D(obj)
object3D.name = "@root" object3D.name = "@root"

View File

@ -12,7 +12,6 @@ import react.RProps
import react.RState import react.RState
import react.dom.div import react.dom.div
import react.dom.findDOMNode import react.dom.findDOMNode
import kotlin.dom.clear
interface ThreeCanvasProps : RProps { interface ThreeCanvasProps : RProps {
var context: Context var context: Context
@ -33,18 +32,21 @@ class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
var canvas: ThreeCanvas? = null var canvas: ThreeCanvas? = null
override fun componentDidMount() { override fun componentDidMount() {
if(canvas == null) {
val element = state.element as? HTMLElement ?: error("Canvas element not found") val element = state.element as? HTMLElement ?: error("Canvas element not found")
val three: ThreePlugin = props.context.plugins.load(ThreePlugin) val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
canvas = three.output(element, props.options ?: Canvas.empty()) canvas = three.output(element, props.options ?: Canvas.empty()).apply {
onClick = props.clickCallback
}
props.canvasCallback?.invoke(canvas) props.canvasCallback?.invoke(canvas)
}
canvas?.render(props.obj) canvas?.render(props.obj)
canvas?.onClick = props.clickCallback
} }
override fun componentWillUnmount() { // override fun componentWillUnmount() {
state.element?.clear() // state.element?.clear()
props.canvasCallback?.invoke(null) // props.canvasCallback?.invoke(null)
} // }
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) { override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
if (prevProps.obj != props.obj) { if (prevProps.obj != props.obj) {

View File

@ -159,6 +159,7 @@ private class GDMLDemoApp : Application {
} }
canvas.select(name) canvas.select(name)
editorElement.visualPropertyEditor(name, child) editorElement.visualPropertyEditor(name, child)
} }
// canvas.clickListener = ::selectElement // canvas.clickListener = ::selectElement

View File

@ -20,6 +20,9 @@ kotlin {
keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io") keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io")
} }
} }
webpackTask {
mode = org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode.PRODUCTION
}
} }
} }

View File

@ -3,10 +3,11 @@ package ru.mipt.npm.muon.monitor
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.js.card import hep.dataforge.js.card
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.editor.configEditor
import hep.dataforge.vis.editor.objectTree import hep.dataforge.vis.editor.objectTree
import hep.dataforge.vis.editor.visualPropertyEditor
import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.specifications.Camera import hep.dataforge.vis.spatial.specifications.Camera
import hep.dataforge.vis.spatial.specifications.Canvas import hep.dataforge.vis.spatial.specifications.Canvas
@ -14,43 +15,27 @@ import hep.dataforge.vis.spatial.three.ThreeCanvas
import hep.dataforge.vis.spatial.three.ThreeCanvasComponent import hep.dataforge.vis.spatial.three.ThreeCanvasComponent
import hep.dataforge.vis.spatial.three.canvasControls import hep.dataforge.vis.spatial.three.canvasControls
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.request.get import io.ktor.client.request.get
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.Json
import react.* import react.*
import react.dom.button import react.dom.*
import react.dom.div
import kotlin.math.PI import kotlin.math.PI
interface MMAppProps : RProps { interface MMAppProps : RProps {
var model: Model var model: Model
var context: Context var context: Context
var connection: HttpClient
} }
interface MMAppState : RState { interface MMAppState : RState {
var model: Model
var selected: Name? var selected: Name?
var canvas: ThreeCanvas? var canvas: ThreeCanvas?
} }
class MMAppComponent : RComponent<MMAppProps, MMAppState>() { class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
private val model = Model()
private val connection = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json(context = Visual3D.serialModule))
}
}
override fun MMAppState.init(props: MMAppProps) {
this.model = props.model
}
private val onSelect: (Name?) -> Unit = { private val onSelect: (Name?) -> Unit = {
setState { setState {
selected = it selected = it
@ -66,7 +51,7 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
} }
override fun RBuilder.render() { override fun RBuilder.render() {
val visual = model.root val visual = props.model.root
val selected = state.selected val selected = state.selected
div("row") { div("row") {
@ -96,15 +81,19 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
div("col-lg-3") { div("col-lg-3") {
div("row") { div("row") {
//settings //settings
state.canvas?.let { canvasControls(it) } state.canvas?.let {
card("Canvas configuration") {
canvasControls(it)
}
}
card("Events") { card("Events") {
button { button {
+"Next" +"Next"
attrs { attrs {
onClickFunction = { onClickFunction = {
GlobalScope.launch { GlobalScope.launch {
val event = connection.get<Event>("http://localhost:8080/event") val event = props.connection.get<Event>("http://localhost:8080/event")
model.displayEvent(event) props.model.displayEvent(event)
} }
} }
} }
@ -113,7 +102,50 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
+"Clear" +"Clear"
attrs { attrs {
onClickFunction = { onClickFunction = {
model.reset() props.model.reset()
}
}
}
}
}
div("row") {
div("container-fluid p-0") {
nav {
attrs {
attributes["aria-label"] = "breadcrumb"
}
ol("breadcrumb") {
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+"World"
attrs {
onClickFunction = {
setState {
this.selected = Name.EMPTY
}
}
}
}
}
if (selected != null) {
val tokens = ArrayList<NameToken>(selected.length)
selected.tokens.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+token.toString()
attrs {
onClickFunction = {
setState {
console.log("Selected = $fullName")
this.selected = fullName
}
}
}
}
}
}
} }
} }
} }
@ -122,13 +154,14 @@ class MMAppComponent : RComponent<MMAppProps, MMAppState>() {
div("row") { div("row") {
//properties //properties
if (selected != null) { if (selected != null) {
val selectedObject = when { val selectedObject: VisualObject? = when {
selected.isEmpty() -> visual selected.isEmpty() -> visual
else -> visual[selected] else -> visual[selected]
} }
if (selectedObject != null) { if (selectedObject != null) {
//TODO replace by explicit breadcrumbs with callback card("Properties") {
visualPropertyEditor(selected, selectedObject, descriptor = VisualObject3D.descriptor) configEditor(selectedObject, descriptor = VisualObject3D.descriptor)
}
} }
} }
} }

View File

@ -21,83 +21,22 @@ private class MMDemoApp : Application {
} }
} }
//TODO introduce react application
override fun start(state: Map<String, Any>) { override fun start(state: Map<String, Any>) {
val context = Global.context("demo") {} val context = Global.context("demo") {}
// val three = context.plugins.load(ThreePlugin)
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page") val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
render(element) { render(element) {
child(MMAppComponent::class) { child(MMAppComponent::class) {
attrs { attrs {
this.model = model model = this@MMDemoApp.model
connection = this@MMDemoApp.connection
this.context = context this.context = context
} }
} }
} }
// //val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
//
// val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
// val settingsElement = document.getElementById("settings")
// ?: error("Element with id 'settings' not found on page")
// val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
// val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
//
// canvasElement.clear()
// val visual = model.root
//
// //output.camera.layers.enable(1)
// val canvas = three.output(canvasElement as HTMLElement)
//
// canvas.camera.layers.set(0)
// canvas.camera.position.z = -2000.0
// canvas.camera.position.y = 500.0
// canvas.camera.lookAt(Vector3(0, 0, 0))
//
// settingsElement.displayCanvasControls(canvas) {
// card("Events") {
// button {
// +"Next"
// onClickFunction = {
// GlobalScope.launch {
// val event = connection.get<Event>("http://localhost:8080/event")
// model.displayEvent(event)
// }
// }
// }
// button {
// +"Clear"
// onClickFunction = {
// model.reset()
// }
// }
// }
// }
//
// var objectTreeContainer: ObjectTreeContainer? = null
//
// fun selectElement(name: Name?) {
// if (name != null) {
// canvas.select(name)
// val child: VisualObject = when {
// name.isEmpty() -> visual
// visual is VisualGroup -> visual[name] ?: return
// else -> return
// }
// editorElement.visualPropertyEditor(name, child, descriptor = VisualObject3D.descriptor)
// objectTreeContainer?.select(name)
// }
// }
//
// val selectElementFunction: (Name?) -> Unit = { name ->
// selectElement(name?.selectable())
// }
//
// canvas.onClick = selectElementFunction
//
// objectTreeContainer = treeElement.renderObjectTree(visual, selectElementFunction)
// canvas.render(visual)
} }
} }