diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ConfigEditorComponent.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ConfigEditorComponent.kt index 4cc7aa3a..76afddda 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ConfigEditorComponent.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ConfigEditorComponent.kt @@ -86,6 +86,10 @@ class ConfigEditorComponent : RComponent() { } } + private val removeValue: (Event) -> Unit = { + props.root.remove(props.name) + } + //TODO replace by separate components private fun RBuilder.valueChooser(value: Value, descriptor: ValueDescriptor?) { val type = descriptor?.type?.firstOrNull() @@ -98,6 +102,15 @@ class ConfigEditorComponent : RComponent() { } type == ValueType.NUMBER -> input(type = InputType.number, classes = "float-right") { 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 onChangeFunction = onValueChange } @@ -195,9 +208,22 @@ class ConfigEditorComponent : RComponent() { } } } + 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") { valueChooser(actualItem.value, descriptorItem as? ValueDescriptor) - //+actualItem.value.toString() } } } @@ -220,23 +246,18 @@ fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, def } fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) { - child(ConfigEditorComponent::class) { - attrs { - root = config - name = Name.EMPTY - this.descriptor = descriptor - this.default = default + div { + child(ConfigEditorComponent::class) { + attrs { + root = config + name = Name.EMPTY + this.descriptor = descriptor + this.default = default + } } } } fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) { - child(ConfigEditorComponent::class) { - attrs { - root = obj.config - name = Name.EMPTY - this.descriptor = descriptor - this.default = default - } - } + configEditor(obj.config, descriptor ?: obj.descriptor, default) } diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ObjectTree.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ObjectTree.kt index 890b7c4b..a419b78d 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ObjectTree.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/ObjectTree.kt @@ -39,7 +39,7 @@ class ObjectTree : RComponent() { } private fun RBuilder.treeLabel(text: String) { - a("#", classes = "tree-label") { + button(classes = "btn btn-link tree-label p-0") { +text attrs { if (props.name == props.selected) { diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/propertyEditor.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/propertyEditor.kt index cc50234f..00f928ee 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/propertyEditor.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/propertyEditor.kt @@ -2,7 +2,6 @@ package hep.dataforge.vis.editor import hep.dataforge.js.card import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.names.Name import hep.dataforge.names.isEmpty @@ -19,10 +18,9 @@ fun RBuilder.visualPropertyEditor( path: Name, item: VisualObject, descriptor: NodeDescriptor? = item.descriptor, - title: String = "Properties", - default: MetaBuilder.() -> Unit = {} + default: Meta? = null ) { - card(title) { + card("Properties") { if (!path.isEmpty()) { nav { 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, item: VisualObject, descriptor: NodeDescriptor? = item.descriptor, - title: String = "Properties", - default: MetaBuilder.() -> Unit = {} + default: Meta? = null ) = render(this) { - visualPropertyEditor(path, item, descriptor, title, default) + this.visualPropertyEditor(path, item, descriptor, default) } \ No newline at end of file diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/valueChoosers.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/valueChoosers.kt new file mode 100644 index 00000000..8cfc4381 --- /dev/null +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/editor/valueChoosers.kt @@ -0,0 +1,11 @@ +package hep.dataforge.vis.editor + +//val TextValueChooser = functionalComponent { +// +// input(type = InputType.number, classes = "float-right") { +// attrs { +// defaultValue = value.string +// onChangeFunction = onValueChange +// } +// } +//} \ No newline at end of file diff --git a/dataforge-vis-common/src/jsMain/resources/css/main.css b/dataforge-vis-common/src/jsMain/resources/css/main.css index b594244f..3069e64e 100644 --- a/dataforge-vis-common/src/jsMain/resources/css/main.css +++ b/dataforge-vis-common/src/jsMain/resources/css/main.css @@ -41,4 +41,8 @@ ul, .tree { .tree-label-selected{ background-color: lightblue; +} + +.no-padding{ + padding: 0; } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt index 266fb7db..a8cb0a11 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt @@ -56,6 +56,13 @@ class Material3D : Scheme() { defineValue(OPACITY_KEY) { type(ValueType.NUMBER) default(1.0) + configure { + "attributes" to { + this["min"] = 0.0 + this["max"] = 1.0 + this["step"] = 0.1 + } + } } defineValue(WIREFRAME_KEY) { type(ValueType.BOOLEAN) diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt index 41237707..b7fcae95 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt @@ -168,11 +168,16 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv } } - override fun render(obj: VisualObject3D, meta: Meta) { - //clear old root + fun clear(){ scene.children.find { it.name == "@root" }?.let { scene.remove(it) } + } + + override fun render(obj: VisualObject3D, meta: Meta) { + //clear old root + clear() + val object3D = three.buildObject3D(obj) object3D.name = "@root" diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasComponent.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasComponent.kt index 0a80d6d2..9a529770 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasComponent.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasComponent.kt @@ -12,7 +12,6 @@ import react.RProps import react.RState import react.dom.div import react.dom.findDOMNode -import kotlin.dom.clear interface ThreeCanvasProps : RProps { var context: Context @@ -33,18 +32,21 @@ class ThreeCanvasComponent : RComponent() { var canvas: ThreeCanvas? = null override fun componentDidMount() { - val element = state.element as? HTMLElement ?: error("Canvas element not found") - val three: ThreePlugin = props.context.plugins.load(ThreePlugin) - canvas = three.output(element, props.options ?: Canvas.empty()) - props.canvasCallback?.invoke(canvas) + if(canvas == null) { + val element = state.element as? HTMLElement ?: error("Canvas element not found") + val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin) + canvas = three.output(element, props.options ?: Canvas.empty()).apply { + onClick = props.clickCallback + } + props.canvasCallback?.invoke(canvas) + } canvas?.render(props.obj) - canvas?.onClick = props.clickCallback } - override fun componentWillUnmount() { - state.element?.clear() - props.canvasCallback?.invoke(null) - } +// override fun componentWillUnmount() { +// state.element?.clear() +// props.canvasCallback?.invoke(null) +// } override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) { if (prevProps.obj != props.obj) { diff --git a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt index f2b40f5e..0c451902 100644 --- a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt +++ b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt @@ -159,6 +159,7 @@ private class GDMLDemoApp : Application { } canvas.select(name) editorElement.visualPropertyEditor(name, child) + } // canvas.clickListener = ::selectElement diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index fd7da96d..b2290bb1 100644 --- a/demo/muon-monitor/build.gradle.kts +++ b/demo/muon-monitor/build.gradle.kts @@ -20,6 +20,9 @@ kotlin { keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io") } } + webpackTask { + mode = org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode.PRODUCTION + } } } 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 8b58e3bf..bbf1f3ee 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 @@ -3,10 +3,11 @@ package ru.mipt.npm.muon.monitor import hep.dataforge.context.Context import hep.dataforge.js.card import hep.dataforge.names.Name +import hep.dataforge.names.NameToken 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.visualPropertyEditor -import hep.dataforge.vis.spatial.Visual3D import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.specifications.Camera 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.canvasControls 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 kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.html.js.onClickFunction -import kotlinx.serialization.json.Json import react.* -import react.dom.button -import react.dom.div +import react.dom.* import kotlin.math.PI interface MMAppProps : RProps { var model: Model var context: Context + var connection: HttpClient } interface MMAppState : RState { - var model: Model var selected: Name? var canvas: ThreeCanvas? } class MMAppComponent : RComponent() { - 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 = { setState { selected = it @@ -66,7 +51,7 @@ class MMAppComponent : RComponent() { } override fun RBuilder.render() { - val visual = model.root + val visual = props.model.root val selected = state.selected div("row") { @@ -86,9 +71,9 @@ class MMAppComponent : RComponent() { this.selected = selected this.clickCallback = onSelect this.canvasCallback = { - setState{ - canvas = it - } + setState { + canvas = it + } } } } @@ -96,15 +81,19 @@ class MMAppComponent : RComponent() { div("col-lg-3") { div("row") { //settings - state.canvas?.let { canvasControls(it) } + state.canvas?.let { + card("Canvas configuration") { + canvasControls(it) + } + } card("Events") { button { +"Next" attrs { onClickFunction = { GlobalScope.launch { - val event = connection.get("http://localhost:8080/event") - model.displayEvent(event) + val event = props.connection.get("http://localhost:8080/event") + props.model.displayEvent(event) } } } @@ -113,7 +102,50 @@ class MMAppComponent : RComponent() { +"Clear" attrs { 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(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() { div("row") { //properties if (selected != null) { - val selectedObject = when { + val selectedObject: VisualObject? = when { selected.isEmpty() -> visual else -> visual[selected] } if (selectedObject != null) { - //TODO replace by explicit breadcrumbs with callback - visualPropertyEditor(selected, selectedObject, descriptor = VisualObject3D.descriptor) + card("Properties") { + configEditor(selectedObject, descriptor = VisualObject3D.descriptor) + } } } } diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt index 22ba41a2..8346df88 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt @@ -21,83 +21,22 @@ private class MMDemoApp : Application { } } + //TODO introduce react application + override fun start(state: Map) { 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") render(element) { child(MMAppComponent::class) { attrs { - this.model = model + model = this@MMDemoApp.model + connection = this@MMDemoApp.connection 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("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) } }