Tree viewer replacement

This commit is contained in:
Alexander Nozik 2019-10-13 12:38:24 +03:00
parent 017995e045
commit 1d5debb8a6
15 changed files with 183 additions and 185 deletions

View File

@ -17,6 +17,9 @@ allprojects {
repositories {
maven("https://dl.bintray.com/pdvrieze/maven")
maven("http://maven.jzy3d.org/releases")
maven("https://kotlin.bintray.com/js-externals")
// maven("https://dl.bintray.com/gbaldeck/kotlin")
// maven("https://dl.bintray.com/rjaros/kotlin")
}
group = "hep.dataforge"

View File

@ -10,6 +10,7 @@ scientifik{
}
val dataforgeVersion: String by rootProject.extra
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
kotlin {
jvm{
@ -31,9 +32,9 @@ kotlin {
jsMain{
dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api("kotlin.js.externals:kotlin-js-jquery:3.2.0-0")
api(npm("text-encoding"))
api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
api(npm("core-js"))
api(npm("bootstrap","4.3.1"))
}
}
}

View File

@ -52,6 +52,8 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
}
}
val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty()
interface MutableVisualGroup : VisualGroup {
/**

View File

@ -1,14 +1,10 @@
package hep.dataforge.vis
package hep.dataforge.js
import kotlin.browser.document
import kotlin.dom.hasClass
external val module: Module
external interface Module {
val hot: Hot?
}
external interface Hot {
val data: dynamic
@ -19,17 +15,31 @@ external interface Hot {
fun dispose(callback: (data: dynamic) -> Unit)
}
external fun require(name: String): dynamic
abstract class ApplicationBase {
open val stateKeys: List<String> get() = emptyList()
abstract fun start(state: Map<String, Any>)
open fun dispose(): Map<String, Any> = emptyMap()
external interface Module {
val hot: Hot?
}
fun startApplication(builder: () -> ApplicationBase) {
fun start(state: dynamic): ApplicationBase? {
/**
* Base interface for applications.
*
* Base interface for applications supporting Hot Module Replacement (HMR).
*/
interface Application {
/**
* Starting point for an application.
* @param state Initial state between Hot Module Replacement (HMR).
*/
fun start(state: Map<String, Any>)
/**
* Ending point for an application.
* @return final state for Hot Module Replacement (HMR).
*/
fun dispose(): Map<String, Any> = emptyMap()
}
fun startApplication(builder: () -> Application) {
fun start(state: dynamic): Application? {
return if (document.body?.hasClass("testApp") == true) {
val application = builder()
@ -42,7 +52,7 @@ fun startApplication(builder: () -> ApplicationBase) {
}
}
var application: ApplicationBase? = null
var application: Application? = null
val state: dynamic = module.hot?.let { hot ->
hot.accept()

View File

@ -1,4 +1,6 @@
package hep.dataforge.vis
package hep.dataforge.js
external fun require(name: String): dynamic
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
val obj: T = js("({})") as T

View File

@ -0,0 +1,23 @@
/* Remove default bullets */
ul, .objTree-subtree {
list-style-type: none;
}
/* Style the caret/arrow */
.objTree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.objTree-caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.objTree-caret-down::before {
transform: rotate(90deg);
}

View File

@ -1,7 +1,10 @@
package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Global
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.js.Application
import hep.dataforge.js.objectTree
import hep.dataforge.js.startApplication
import hep.dataforge.names.NameToken
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D
@ -9,13 +12,11 @@ import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.attachChildren
import hep.dataforge.vis.spatial.editor.propertyEditor
import hep.dataforge.vis.spatial.editor.threeOutputConfig
import hep.dataforge.vis.spatial.editor.visualObjectTree
import hep.dataforge.vis.spatial.gdml.GDMLTransformer
import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.startApplication
import kotlinx.html.dom.append
import kotlinx.html.js.p
import org.w3c.dom.DragEvent
@ -29,7 +30,7 @@ import kotlin.browser.document
import kotlin.browser.window
import kotlin.dom.clear
private class GDMLDemoApp : ApplicationBase() {
private class GDMLDemoApp : Application {
/**
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
*/
@ -46,7 +47,6 @@ private class GDMLDemoApp : ApplicationBase() {
event.stopPropagation()
event.preventDefault()
val file = (event.dataTransfer?.files as FileList)[0]
?: throw RuntimeException("Failed to load file")
@ -153,7 +153,9 @@ private class GDMLDemoApp : ApplicationBase() {
output.camera.layers.set(0)
layers.threeOutputConfig(output)
tree.visualObjectTree(visual, editor::propertyEditor)
//tree.visualObjectTree(visual, editor::propertyEditor)
tree.objectTree(NameToken("World"),visual, editor::propertyEditor)
output.render(visual)
message(null)
@ -166,10 +168,6 @@ private class GDMLDemoApp : ApplicationBase() {
}
}
override fun dispose(): Map<String, Any> {
return super.dispose()
}
}
fun main() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,3 +11,38 @@
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Remove default bullets */
ul, .objTree-subtree {
list-style-type: none;
}
/* Style the caret/arrow */
.objTree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
.objTree-label {
cursor: pointer;
}
/* Create the caret/arrow with a unicode, and style it */
.objTree-caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
}
.objTree-leaf::before {
content: "\25C6";
color: black;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.objTree-caret-down::before {
transform: rotate(90deg);
}

View File

@ -4,13 +4,9 @@
<meta charset="utf-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">-->
<title>Three js demo for particle physics</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/inspire-tree-light.min.css">
<link rel="stylesheet" href="css/jsoneditor.min.css">
<script type="text/javascript" src="main.bundle.js"></script>
</head>
@ -37,12 +33,5 @@
<div class="col-lg-3" id="editor"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>

View File

@ -0,0 +1,75 @@
package hep.dataforge.js
import hep.dataforge.names.NameToken
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.isEmpty
import hep.dataforge.vis.spatial.editor.card
import kotlinx.html.TagConsumer
import kotlinx.html.dom.append
import kotlinx.html.js.*
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement
import kotlin.dom.clear
fun Element.objectTree(
token: NameToken,
obj: VisualObject,
clickCallback: (VisualObject) -> Unit = {}
) {
clear()
append {
card("Object tree") {
subTree(token, obj, clickCallback)
}
}
}
private fun TagConsumer<HTMLElement>.subTree(
token: NameToken,
obj: VisualObject,
clickCallback: (VisualObject) -> Unit
) {
if (obj is VisualGroup && !obj.isEmpty) {
lateinit var toggle: HTMLSpanElement
div("d-inline-block text-truncate") {
toggle = span("objTree-caret")
label("objTree-label") {
+token.toString()
onClickFunction = { clickCallback(obj) }
}
}
val subtree = ul("objTree-subtree")
toggle.onclick = {
toggle.classList.toggle("objTree-caret-down")
subtree.apply {
if (toggle.classList.contains("objTree-caret-down")) {
obj.children.entries
.filter { !it.key.toString().startsWith("@") }
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
.forEach { (token, child) ->
append {
li().apply {
subTree(token, child, clickCallback)
}
}
}
} else {
this.clear()
}
}
//jQuery(subtree).asDynamic().collapse("toggle")
}
} else {
div("d-inline-block text-truncate") {
span("objTree-leaf")
label("objTree-label") {
+token.toString()
onClickFunction = { clickCallback(obj) }
}
}
}
}

View File

@ -1,136 +0,0 @@
@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
package hep.dataforge.vis.spatial.editor
import hep.dataforge.meta.string
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.getProperty
import hep.dataforge.vis.jsObject
import hep.dataforge.vis.spatial.Proxy
import hep.dataforge.vis.spatial.visible
import info.laht.threekt.loaders.Cache.clear
import kotlinx.html.div
import kotlinx.html.dom.append
import org.w3c.dom.Element
import kotlin.js.json
operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
private fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
val config = (json(
"checkbox" to json(
"autoCheckChildren" to false
)
) as Config).apply(block)
return InspireTree(config)
}
private fun VisualObject.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }): InspireTree {
val map = HashMap<String, VisualObject>()
fun generateNodeConfig(item: VisualObject, fullName: Name): NodeConfig {
val title = item.getProperty("title").string ?: fullName.last()?.toString() ?: "root"
val className = if (item is Proxy) {
item.prototype::class.toString()
} else {
item::class.toString()
}.replace("class ", "")
val text: String = if (title.startsWith("@")) {
"[$className}]"
} else {
"$title[$className}]"
}
return json(
"children" to if ((item as? VisualGroup)?.children?.isEmpty() != false) {
emptyArray<NodeConfig>()
} else {
true
},
"text" to text,
"id" to fullName.toString(),
"itree" to json(
"state" to json(
"checked" to (item.visible ?: true)
)
)
) as NodeConfig
}
fun TreeNode.fillChildren(group: VisualObject, groupName: Name) {
if(group is VisualGroup) {
group.children.forEach { (token, obj) ->
if (!token.body.startsWith("@")) {
val name = groupName + token
val nodeConfig = generateNodeConfig(obj, name)
val childNode = addChild(nodeConfig)
map[childNode.id] = obj
if (obj is VisualGroup) {
childNode.fillChildren(obj, name)
}
}
}
}
}
val inspireTree = createInspireTree {
}
val nodeConfig = generateNodeConfig(this, EmptyName)
val rootNode = inspireTree.addNode(nodeConfig)
map[rootNode.id] = this
rootNode.fillChildren(this, EmptyName)
// inspireTree.on("node.selected") { node: TreeNode, isLoadEvent: Boolean ->
// if (!isLoadEvent) {
// map[node.id]?.selected = node.selected()
// }
// }
//
// inspireTree.on("node.deselect") { node: TreeNode ->
// map[node.id]?.selected = node.selected()
// }
inspireTree.on("node.checked") { node: TreeNode, isLoadEvent: Boolean ->
if (!isLoadEvent) {
map[node.id]?.visible = node.checked()
}
}
inspireTree.on("node.unchecked") { node: TreeNode ->
if (!node.indeterminate()) {
map[node.id]?.visible = node.checked()
}
}
inspireTree.on("node.focused") { node: TreeNode, isLoadEvent: Boolean ->
if (!isLoadEvent) {
onFocus(map[node.id], node.id)
}
}
inspireTree.collapseDeep()
return inspireTree
}
fun Element.visualObjectTree(group: VisualObject, onFocus: (VisualObject?, String?) -> Unit) {
clear()
append {
card("Visual object tree") {
val domConfig = jsObject<DomConfig> {
target = div()
showCheckboxes = false
}
InspireTreeDOM(group.toTree(onFocus), domConfig)
}
}
}

View File

@ -1,10 +1,10 @@
package hep.dataforge.vis.spatial.editor
import hep.dataforge.io.toJson
import hep.dataforge.js.jsObject
import hep.dataforge.meta.*
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.findStyle
import hep.dataforge.vis.jsObject
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
@ -22,7 +22,7 @@ import kotlin.dom.clear
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
fun Element.propertyEditor(item: VisualObject?, name: String?) {
fun Element.propertyEditor(item: VisualObject?) {
clear()
if (item != null) {
append {

View File

@ -1,10 +1,10 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.ContextBuilder
import hep.dataforge.vis.ApplicationBase
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.startApplication
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@ -15,9 +15,7 @@ import kotlin.math.sin
import kotlin.random.Random
private class ThreeDemoApp : ApplicationBase() {
override val stateKeys: List<String> = emptyList()
private class ThreeDemoApp : Application {
override fun start(state: Map<String, Any>) {
@ -141,7 +139,7 @@ private class ThreeDemoApp : ApplicationBase() {
thickness = 208.0
rotationX = it * PI2 / 20
color(Colors.green)
//rotationY = it * PI2 / 20
//rotationY = it * PI2 / 20
}
}
}