MMApp fully on react (with crutches)

This commit is contained in:
Alexander Nozik 2020-04-16 12:46:54 +03:00
parent e6879fee15
commit ea73650b50
31 changed files with 653 additions and 1475 deletions

View File

@ -40,10 +40,6 @@ kotlin {
api(npm("inline-style-prefixer")) api(npm("inline-style-prefixer"))
api(npm("source-map-resolve","0.6.0")) api(npm("source-map-resolve","0.6.0"))
api(npm("bootstrap","4.4.1"))
api(npm("popper.js","1.14.7"))
api(npm("jquery","3.5.0"))
//api(npm("jsoneditor", "8.6.1"))
api(npm("file-saver","2.0.2")) api(npm("file-saver","2.0.2"))
} }
} }

View File

@ -0,0 +1,19 @@
package hep.dataforge.vis
import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
/**
* Return nearest selectable parent [Name]
*/
tailrec fun Name.selectable(): Name? = when {
isEmpty() -> {
null
}
last()?.body?.startsWith("@") != true -> {
this
}
else -> {
cutLast().selectable()
}
}

View File

@ -0,0 +1,121 @@
package hep.dataforge.js
import kotlinx.html.*
import kotlinx.html.js.div
import org.w3c.dom.HTMLElement
import react.RBuilder
import react.dom.*
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
div("card w-100") {
div("card-body") {
h3(classes = "card-title") { +title }
block()
}
}
}
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) {
div("card w-100") {
div("card-body") {
h3(classes = "card-title") { +title }
block()
}
}
}
fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
div("container-fluid") {
div("accordion") {
this.id = id
elements.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse"
div("card") {
div("card-header") {
this.id = headerID
h5("mb-0") {
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
attributes["data-toggle"] = "collapse"
attributes["data-target"] = "#$collapseID"
attributes["aria-expanded"] = "false"
attributes["aria-controls"] = collapseID
+title
}
}
}
div("collapse") {
this.id = collapseID
attributes["aria-labelledby"] = headerID
attributes["data-parent"] = "#$id"
div("card-body", block = builder)
}
}
}
}
}
}
typealias AccordionBuilder = MutableList<Pair<String, DIV.() -> Unit>>
fun AccordionBuilder.entry(title: String, builder: DIV.() -> Unit) {
add(title to builder)
}
fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
accordion(id, list)
}
fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>) {
div("container-fluid") {
div("accordion") {
attrs {
this.id = id
}
elements.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse"
div("card") {
div("card-header") {
attrs {
this.id = headerID
}
h5("mb-0") {
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
attrs {
attributes["data-toggle"] = "collapse"
attributes["data-target"] = "#$collapseID"
attributes["aria-expanded"] = "false"
attributes["aria-controls"] = collapseID
}
+title
}
}
}
div("collapse") {
attrs {
this.id = collapseID
attributes["aria-labelledby"] = headerID
attributes["data-parent"] = "#$id"
}
div("card-body", block = builder)
}
}
}
}
}
}
typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
add(title to builder)
}
fun RBuilder.accordion(id: String, builder: RAccordionBuilder.() -> Unit) {
val list = ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
accordion(id, list)
}

View File

@ -15,3 +15,4 @@ fun <T> RComponent<*, *>.initState(init: () -> T): ReadWriteProperty<RComponent<
pair.second(value) pair.second(value)
} }
} }

View File

@ -34,10 +34,10 @@ class MetaViewerComponent : RComponent<MetaViewerProps, TreeState>() {
override fun RBuilder.render() { override fun RBuilder.render() {
div("d-inline-block text-truncate") { div("d-inline-block text-truncate") {
if (props.meta.items.isNotEmpty()) { if (props.meta.items.isNotEmpty()) {
span("objTree-caret") { span("tree-caret") {
attrs { attrs {
if (state.expanded) { if (state.expanded) {
classes += "objTree-caret-down" classes += "tree-caret-down"
} }
onClickFunction = onClick onClickFunction = onClick
} }

View File

@ -1,7 +1,9 @@
package hep.dataforge.vis.editor package hep.dataforge.vis.editor
import hep.dataforge.js.card
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.vis.VisualGroup import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.isEmpty import hep.dataforge.vis.isEmpty
@ -14,6 +16,7 @@ import react.dom.*
interface ObjectTreeProps : RProps { interface ObjectTreeProps : RProps {
var name: Name var name: Name
var selected: Name?
var obj: VisualObject var obj: VisualObject
var clickCallback: (Name) -> Unit var clickCallback: (Name) -> Unit
} }
@ -22,39 +25,49 @@ interface TreeState : RState {
var expanded: Boolean var expanded: Boolean
} }
class ObjectTreeComponent : RComponent<ObjectTreeProps, TreeState>() { class ObjectTree : RComponent<ObjectTreeProps, TreeState>() {
override fun TreeState.init() { override fun TreeState.init(props: ObjectTreeProps) {
expanded = false expanded = props.selected?.startsWith(props.name) ?: false
} }
private val onClick: (Event) -> Unit = { private val onClick: (Event) -> Unit = {
setState { setState {
expanded = !expanded expanded = !expanded
} }
} }
private fun RBuilder.treeLabel(text: String) {
a("#", classes = "tree-label") {
+text
attrs {
if (props.name == props.selected) {
classes += "tree-label-selected"
}
onClickFunction = { props.clickCallback(props.name) }
}
}
}
override fun RBuilder.render() { override fun RBuilder.render() {
val token = props.name.last()?.toString() ?: "World" val token = props.name.last()?.toString() ?: "World"
val obj = props.obj val obj = props.obj
//display as node if any child is visible //display as node if any child is visible
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) { if (obj is VisualGroup) {
div("d-inline-block text-truncate") { div("d-inline-block text-truncate") {
span("tree-caret") { if (obj.children.any { !it.key.body.startsWith("@") }) {
attrs { span("tree-caret") {
if (state.expanded) { attrs {
classes += "tree-caret-down" if (state.expanded) {
classes += "tree-caret-down"
}
onClickFunction = onClick
} }
onClickFunction = onClick
}
}
a("#",classes = "tree-label") {
+token
attrs {
onClickFunction = { props.clickCallback(props.name) }
} }
} }
treeLabel(token)
} }
if (state.expanded) { if (state.expanded) {
ul("tree") { ul("tree") {
@ -63,10 +76,11 @@ class ObjectTreeComponent : RComponent<ObjectTreeProps, TreeState>() {
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true } .sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
li("tree-item") { li("tree-item") {
child(ObjectTreeComponent::class) { child(ObjectTree::class) {
attrs { attrs {
name = props.name + childToken name = props.name + childToken
this.obj = child this.obj = child
this.selected = props.selected
clickCallback = props.clickCallback clickCallback = props.clickCallback
} }
} }
@ -77,35 +91,40 @@ class ObjectTreeComponent : RComponent<ObjectTreeProps, TreeState>() {
} else { } else {
div("d-inline-block text-truncate") { div("d-inline-block text-truncate") {
span("tree-leaf") {} span("tree-leaf") {}
a("#",classes = "tree-label") { treeLabel(token)
+token }
attrs { }
onClickFunction = { props.clickCallback(props.name) } }
} }
}
fun Element.renderObjectTree(
visualObject: VisualObject,
clickCallback: (Name) -> Unit = {}
) = render(this){
card("Object tree") {
child(ObjectTree::class) {
attrs {
this.name = Name.EMPTY
this.obj = visualObject
this.selected = null
this.clickCallback = clickCallback
} }
} }
} }
} }
fun RBuilder.objectTree( fun RBuilder.objectTree(
obj: VisualObject, visualObject: VisualObject,
selected: Name? = null,
clickCallback: (Name) -> Unit = {} clickCallback: (Name) -> Unit = {}
) = card("Object tree") { ){
child(ObjectTreeComponent::class) { child(ObjectTree::class) {
attrs { attrs {
name = Name.EMPTY this.name = Name.EMPTY
this.obj = obj this.obj = visualObject
this.selected = selected
this.clickCallback = clickCallback this.clickCallback = clickCallback
} }
} }
} }
fun Element.objectTree(
obj: VisualObject,
clickCallback: (Name) -> Unit = {}
) {
render(this) {
objectTree(obj, clickCallback)
}
}

View File

@ -1,73 +0,0 @@
package hep.dataforge.vis.editor
import kotlinx.html.*
import kotlinx.html.js.div
import org.w3c.dom.HTMLElement
import react.RBuilder
import react.ReactElement
import react.dom.div
import react.dom.h3
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
div("card w-100") {
div("card-body") {
h3(classes = "card-title") { +title }
block()
}
}
}
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit): ReactElement = div("card w-100") {
div("card-body") {
h3(classes = "card-title") { +title }
block()
}
}
fun TagConsumer<HTMLElement>.accordion(id: String, elements: Map<String, DIV.() -> Unit>) {
div("container-fluid") {
div("accordion") {
this.id = id
elements.entries.forEachIndexed { index, (title, builder) ->
val headerID = "${id}-${index}-heading"
val collapseID = "${id}-${index}-collapse"
div("card") {
div("card-header") {
this.id = headerID
h5("mb-0") {
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
attributes["data-toggle"] = "collapse"
attributes["data-target"] = "#$collapseID"
attributes["aria-expanded"] = "false"
attributes["aria-controls"] = collapseID
+title
}
}
}
div("collapse") {
this.id = collapseID
attributes["aria-labelledby"] = headerID
attributes["data-parent"] = "#$id"
div("card-body", block = builder)
}
}
}
}
}
}
class AccordionBuilder {
private val map = HashMap<String, DIV.() -> Unit>()
fun entry(title: String, block: DIV.() -> Unit) {
map[title] = block
}
fun build(consumer: TagConsumer<HTMLElement>, id: String) {
consumer.accordion(id, map)
}
}
fun TagConsumer<HTMLElement>.accordion(id: String, block: AccordionBuilder.() -> Unit) {
AccordionBuilder().apply(block).build(this, id)
}

View File

@ -1,77 +0,0 @@
package hep.dataforge.vis.editor
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.isEmpty
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.displayObjectTree(
// obj: VisualObject,
// clickCallback: (Name) -> Unit = {}
//) {
// clear()
// append {
// card("Object tree") {
// subTree(Name.EMPTY, obj, clickCallback)
// }
// }
//}
//
private fun TagConsumer<HTMLElement>.subTree(
name: Name,
obj: VisualObject,
clickCallback: (Name) -> Unit
) {
val token = name.last()?.toString()?:"World"
//display as node if any child is visible
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
lateinit var toggle: HTMLSpanElement
div("d-inline-block text-truncate") {
toggle = span("objTree-caret")
label("objTree-label") {
+token
onClickFunction = { clickCallback(name) }
}
}
val subtree = ul("objTree-subtree")
toggle.onclick = {
toggle.classList.toggle("objTree-caret-down")
subtree.apply {
//If expanded, add children dynamically
if (toggle.classList.contains("objTree-caret-down")) {
obj.children.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
.forEach { (childToken, child) ->
append {
li().apply {
subTree(name + childToken, child, clickCallback)
}
}
}
} else {
// if not, clear them to conserve memory on very long lists
this.clear()
}
}
}
} else {
div("d-inline-block text-truncate") {
span("objTree-leaf")
label("objTree-label") {
+token
onClickFunction = { clickCallback(name) }
}
}
}
}

View File

@ -1,185 +0,0 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION"
)
package hep.dataforge.vis.editor
import org.w3c.dom.HTMLElement
external interface Node {
var field: String
var value: String? get() = definedExternally; set(value) = definedExternally
var path: dynamic
}
external interface NodeName {
var path: Array<String>
var type: dynamic /* 'object' | 'array' */
var size: Number
}
external interface ValidationError {
var path: dynamic
var message: String
}
external interface Template {
var text: String
var title: String
var className: String? get() = definedExternally; set(value) = definedExternally
var field: String
var value: Any
}
external interface `T$6` {
var startFrom: Number
var options: Array<String>
}
external interface AutoCompleteOptions {
var confirmKeys: Array<Number>? get() = definedExternally; set(value) = definedExternally
var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally
// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally
}
external interface SelectionPosition {
var row: Number
var column: Number
}
external interface SerializableNode {
var value: Any
var path: dynamic
}
external interface Color {
var rgba: Array<Number>
var hsla: Array<Number>
var rgbString: String
var rgbaString: String
var hslString: String
var hslaString: String
var hex: String
}
//external interface `T$0` {
// var field: Boolean
// var value: Boolean
//}
//
//external interface `T$1` {
// @nativeGetter
// operator fun get(key: String): String?
//
// @nativeSetter
// operator fun set(key: String, value: String)
//}
//external interface Languages {
// @nativeGetter
// operator fun get(lang: String): `T$1`?
//
// @nativeSetter
// operator fun set(lang: String, value: `T$1`)
//}
external interface JSONEditorOptions {
// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally
// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally
var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally
var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally
var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally
var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally
var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally
var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally
var history: Boolean? get() = definedExternally; set(value) = definedExternally
var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
var modes: Array<dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */>? get() = definedExternally; set(value) = definedExternally
var name: String? get() = definedExternally; set(value) = definedExternally
var schema: Any? get() = definedExternally; set(value) = definedExternally
var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally
var search: Boolean? get() = definedExternally; set(value) = definedExternally
var indentation: Number? get() = definedExternally; set(value) = definedExternally
var theme: String? get() = definedExternally; set(value) = definedExternally
var templates: Array<Template>? get() = definedExternally; set(value) = definedExternally
var autocomplete: AutoCompleteOptions? get() = definedExternally; set(value) = definedExternally
var mainMenuBar: Boolean? get() = definedExternally; set(value) = definedExternally
var navigationBar: Boolean? get() = definedExternally; set(value) = definedExternally
var statusBar: Boolean? get() = definedExternally; set(value) = definedExternally
var onTextSelectionChange: ((start: SelectionPosition, end: SelectionPosition, text: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onSelectionChange: ((start: SerializableNode, end: SerializableNode) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onEvent: ((node: Node, event: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var colorPicker: Boolean? get() = definedExternally; set(value) = definedExternally
var onColorPicker: ((parent: HTMLElement, color: String, onChange: (color: Color) -> Unit) -> Unit)? get() = definedExternally; set(value) = definedExternally
var timestampTag: Boolean? get() = definedExternally; set(value) = definedExternally
var language: String? get() = definedExternally; set(value) = definedExternally
//var languages: Languages? get() = definedExternally; set(value) = definedExternally
var modalAnchor: HTMLElement? get() = definedExternally; set(value) = definedExternally
var enableSort: Boolean? get() = definedExternally; set(value) = definedExternally
var enableTransform: Boolean? get() = definedExternally; set(value) = definedExternally
var maxVisibleChilds: Number? get() = definedExternally; set(value) = definedExternally
}
external interface JsonPath {
var path: dynamic
}
external interface EditorSelection {
var start: SerializableNode
var end: SerializableNode
}
external interface TextSelection {
var start: SelectionPosition
var end: SelectionPosition
var text: String
}
@JsModule("jsoneditor")
@JsNonModule
external open class JSONEditor(
container: HTMLElement,
options: JSONEditorOptions? = definedExternally /* null */,
json: dynamic = definedExternally /* null */
) {
open fun collapseAll()
open fun destroy()
open fun expandAll()
open fun focus()
open fun get(): Any
open fun getMode(): dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
open fun getName(): String?
open fun getNodesByRange(start: JsonPath, end: JsonPath): Array<SerializableNode>
open fun getSelection(): EditorSelection
open fun getText(): String
open fun getTextSelection(): TextSelection
open fun refresh()
open fun set(json: Any)
open fun setMode(mode: String /* 'tree' */)
open fun setMode(mode: String /* 'view' */)
open fun setMode(mode: String /* 'form' */)
open fun setMode(mode: String /* 'code' */)
open fun setMode(mode: String /* 'text' */)
open fun setName(name: String? = definedExternally /* null */)
open fun setSchema(schema: Any?, schemaRefs: Any? = definedExternally /* null */)
open fun setSelection(start: JsonPath, end: JsonPath)
open fun setText(jsonString: String)
open fun setTextSelection(start: SelectionPosition, end: SelectionPosition)
open fun update(json: Any)
open fun updateText(jsonString: String)
companion object {
var VALID_OPTIONS: Array<String>
// var ace: AceAjax.Ace
// var Ajv: Ajv
var VanillaPicker: Any
}
}

View File

@ -1,5 +1,6 @@
package hep.dataforge.vis.editor package hep.dataforge.vis.editor
import hep.dataforge.js.card
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
@ -8,92 +9,36 @@ import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualObject import hep.dataforge.vis.VisualObject
import org.w3c.dom.Element import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.ReactElement
import react.dom.li import react.dom.li
import react.dom.nav import react.dom.nav
import react.dom.ol import react.dom.ol
import react.dom.render import react.dom.render
import kotlin.collections.set import kotlin.collections.set
////FIXME something rotten in JS-Meta converter
//fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
//
////TODO add node descriptor instead of configuring property selector
//fun Element.displayPropertyEditor(
// name: Name,
// item: VisualObject,
// propertySelector: (VisualObject) -> Meta = { it.config }
//) {
// clear()
//
// append {
// card("Properties") {
// if (!name.isEmpty()) {
// nav {
// attributes["aria-label"] = "breadcrumb"
// ol("breadcrumb") {
// name.tokens.forEach { token ->
// li("breadcrumb-item") {
// +token.toString()
// }
// }
// }
// }
// }
// val dMeta: dynamic = propertySelector(item).toDynamic()
// val options: JSONEditorOptions = jsObject {
// mode = "form"
// onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
// }
// JSONEditor(div(), options, dMeta)
// }
//
// val styles = item.styles
// if (styles.isNotEmpty()) {
// card("Styles") {
// item.styles.forEach { style ->
// val styleMeta = item.findStyle(style)
// h4("container") { +style }
// if (styleMeta != null) {
// div("container").apply {
// val options: JSONEditorOptions = jsObject {
// mode = "view"
// }
// JSONEditor(
// this,
// options,
// styleMeta.toDynamic()
// )
// }
// }
// }
// }
// }
// }
//}
fun RBuilder.visualPropertyEditor( fun RBuilder.visualPropertyEditor(
path: Name, path: Name,
item: VisualObject, item: VisualObject,
descriptor: NodeDescriptor? = item.descriptor, descriptor: NodeDescriptor? = item.descriptor,
title: String = "Properties", title: String = "Properties",
default: MetaBuilder.() -> Unit = {} default: MetaBuilder.() -> Unit = {}
): ReactElement = card(title) { ) {
if (!path.isEmpty()) { card(title) {
nav { if (!path.isEmpty()) {
attrs { nav {
attributes["aria-label"] = "breadcrumb" attrs {
} attributes["aria-label"] = "breadcrumb"
ol("breadcrumb") { }
path.tokens.forEach { token -> ol("breadcrumb") {
li("breadcrumb-item") { path.tokens.forEach { token ->
+token.toString() li("breadcrumb-item") {
+token.toString()
}
} }
} }
} }
} }
configEditor(item, descriptor, Meta(default))
} }
configEditor(item, descriptor, Meta(default))
} }
fun Element.visualPropertyEditor( fun Element.visualPropertyEditor(

View File

@ -38,3 +38,7 @@ ul, .tree {
.tree-label-inactive { .tree-label-inactive {
color: gray; color: gray;
} }
.tree-label-selected{
background-color: lightblue;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,10 @@ plugins {
serialization() serialization()
kotlin { kotlin {
js {
useCommonJs()
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {

View File

@ -14,6 +14,7 @@ import hep.dataforge.vis.spatial.specifications.Camera
import hep.dataforge.vis.spatial.specifications.Canvas import hep.dataforge.vis.spatial.specifications.Canvas
import hep.dataforge.vis.spatial.specifications.Controls import hep.dataforge.vis.spatial.specifications.Controls
import hep.dataforge.vis.spatial.three.ThreeMaterials.HIGHLIGHT_MATERIAL import hep.dataforge.vis.spatial.three.ThreeMaterials.HIGHLIGHT_MATERIAL
import hep.dataforge.vis.spatial.three.ThreeMaterials.SELECTED_MATERIAL
import info.laht.threekt.WebGLRenderer import info.laht.threekt.WebGLRenderer
import info.laht.threekt.cameras.PerspectiveCamera import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
@ -23,6 +24,7 @@ import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls import info.laht.threekt.external.controls.TrackballControls
import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.helpers.AxesHelper import info.laht.threekt.helpers.AxesHelper
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.math.Vector2 import info.laht.threekt.math.Vector2
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
@ -51,7 +53,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
private val raycaster = Raycaster() private val raycaster = Raycaster()
private val mousePosition: Vector2 = Vector2() private val mousePosition: Vector2 = Vector2()
var clickListener: ((Name) -> Unit)? = null var onClick: ((Name?) -> Unit)? = null
val axes = AxesHelper(canvas.axes.size.toInt()).apply { val axes = AxesHelper(canvas.axes.size.toInt()).apply {
visible = canvas.axes.visible visible = canvas.axes.visible
@ -63,6 +65,8 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
val camera = buildCamera(canvas.camera) val camera = buildCamera(canvas.camera)
private var picked: Object3D? = null
init { init {
element.clear() element.clear()
@ -75,29 +79,26 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
} }
}, false) }, false)
element.addEventListener("mousedown", { event -> element.addEventListener("mousedown", {
val mesh = pick() val picked = pick()
if (mesh != null) { onClick?.invoke(picked?.fullName())
val name = mesh.fullName()
clickListener?.invoke(name)
}
}, false) }, false)
camera.aspect = 1.0 camera.aspect = 1.0
val renderer = WebGLRenderer { antialias = true }.apply { val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(Colors.skyblue, 1) setClearColor(Colors.skyblue, 1)
} }
addControls(renderer.domElement, canvas.controls) addControls(renderer.domElement, canvas.controls)
fun animate() { fun animate() {
val mesh = pick() val picked = pick()
if (mesh != null && highlighted != mesh) { if (picked != null && this.picked != picked) {
highlighted?.toggleHighlight(false) this.picked?.toggleHighlight(false,HIGHLIGHT_NAME, HIGHLIGHT_MATERIAL)
mesh.toggleHighlight(true) picked.toggleHighlight(true, HIGHLIGHT_NAME, HIGHLIGHT_MATERIAL)
this.picked = picked
} }
window.requestAnimationFrame { window.requestAnimationFrame {
@ -118,18 +119,6 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
animate() animate()
} }
private fun pick(): Mesh? {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera(mousePosition, camera)
// calculate objects intersecting the picking ray
return root?.let { root ->
val intersects = raycaster.intersectObject(root, true)
val intersect = intersects.firstOrNull()
intersect?.`object` as? Mesh
}
}
/** /**
* Resolve full name of the object relative to the global root * Resolve full name of the object relative to the global root
*/ */
@ -142,6 +131,25 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
} }
} }
private fun Object3D.isStatic(): Boolean {
return false
}
private fun Object3D?.upTrace(): Object3D? = if (this?.name?.startsWith("@") == true) parent else this
private fun pick(): Object3D? {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera(mousePosition, camera)
// calculate objects intersecting the picking ray
return root?.let { root ->
val intersects = raycaster.intersectObject(root, true)
val obj = intersects.map { it.`object` }.firstOrNull { !it.isStatic() }
obj.upTrace()
}
}
private fun buildCamera(spec: Camera) = PerspectiveCamera( private fun buildCamera(spec: Camera) = PerspectiveCamera(
spec.fov, spec.fov,
1.0, 1.0,
@ -173,42 +181,61 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
root = object3D root = object3D
} }
private var highlighted: Mesh? = null private var highlighted: Object3D? = null
/** /**
* Toggle highlight for the given [Mesh] object * Toggle highlight for the given [Mesh] object
*/ */
private fun Mesh.toggleHighlight(highlight: Boolean) { private fun Object3D.toggleHighlight(
if (highlight) { highlight: Boolean,
val edges = LineSegments( edgesName: String,
EdgesGeometry(geometry as BufferGeometry), material: LineBasicMaterial = SELECTED_MATERIAL
HIGHLIGHT_MATERIAL ) {
).apply { if (userData[DO_NOT_HIGHLIGHT_TAG] == true) {
name = "@highlight" return
}
if (this is Mesh) {
if (highlight) {
val edges = LineSegments(
EdgesGeometry(geometry as BufferGeometry),
material
).apply {
name = edgesName
}
add(edges)
} else {
val highlightEdges = children.find { it.name == edgesName }
highlightEdges?.let { remove(it) }
} }
add(edges)
highlighted = this
} else { } else {
val highlightEdges = children.find { it.name == "@highlight" } children.filter { it.name != edgesName }.forEach {
highlightEdges?.let { remove(it) } it.toggleHighlight(highlight, edgesName, material)
}
} }
} }
/** /**
* Toggle highlight for element with given name * Toggle highlight for element with given name
*/ */
fun highlight(name: Name?) { fun select(name: Name?) {
if (name == null) { if (name == null) {
highlighted?.toggleHighlight(false) highlighted?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
highlighted = null highlighted = null
return return
} }
val mesh = root?.findChild(name) as? Mesh val obj = root?.findChild(name)
if (mesh != null && highlighted != mesh) { if (obj != null && highlighted != obj) {
highlighted?.toggleHighlight(false) highlighted?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
mesh.toggleHighlight(true) obj.toggleHighlight(true, SELECT_NAME, SELECTED_MATERIAL)
highlighted = obj
} }
} }
companion object {
const val DO_NOT_HIGHLIGHT_TAG = "doNotHighlight"
private const val HIGHLIGHT_NAME = "@highlight"
private const val SELECT_NAME = "@select"
}
} }
fun ThreePlugin.output(element: HTMLElement, spec: Canvas = Canvas.empty()): ThreeCanvas = fun ThreePlugin.output(element: HTMLElement, spec: Canvas = Canvas.empty()): ThreeCanvas =
@ -216,4 +243,3 @@ fun ThreePlugin.output(element: HTMLElement, spec: Canvas = Canvas.empty()): Thr
fun ThreePlugin.render(element: HTMLElement, obj: VisualObject3D, spec: Canvas = Canvas.empty()): Unit = fun ThreePlugin.render(element: HTMLElement, obj: VisualObject3D, spec: Canvas = Canvas.empty()): Unit =
output(element, spec).render(obj) output(element, spec).render(obj)

View File

@ -1,55 +1,73 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.context.Global import hep.dataforge.context.Context
import hep.dataforge.names.Name
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.specifications.Canvas import hep.dataforge.vis.spatial.specifications.Canvas
import kotlinx.html.id import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import react.RBuilder import react.RBuilder
import react.RComponent import react.RComponent
import react.RProps import react.RProps
import react.RState import react.RState
import react.dom.div import react.dom.div
import kotlin.browser.document import react.dom.findDOMNode
import kotlin.dom.clear import kotlin.dom.clear
interface ThreeCanvasProps : RProps { interface ThreeCanvasProps : RProps {
var context: Context
var obj: VisualObject3D var obj: VisualObject3D
var canvasId: String var options: Canvas?
var options: Canvas var selected: Name?
var clickCallback: (Name?) -> Unit
var canvasCallback: ((ThreeCanvas?) -> Unit)?
} }
class ThreeCanvasComponent : RComponent<ThreeCanvasProps, RState>() { interface ThreeCanvasState : RState {
var element: Element?
// var canvas: ThreeCanvas?
}
private val three: ThreePlugin = Global.plugins.fetch(ThreePlugin) class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
var canvas: ThreeCanvas? = null
override fun componentDidMount() { override fun componentDidMount() {
val element = document.getElementById(props.canvasId) as? HTMLElement val element = state.element as? HTMLElement ?: error("Canvas element not found")
?: error("Element with id 'canvas' not found on page") val three: ThreePlugin = props.context.plugins.load(ThreePlugin)
val output = three.output(element, props.options) canvas = three.output(element, props.options ?: Canvas.empty())
output.render(props.obj) props.canvasCallback?.invoke(canvas)
canvas?.render(props.obj)
canvas?.onClick = props.clickCallback
} }
override fun componentWillUnmount() { override fun componentWillUnmount() {
val element = document.getElementById(props.canvasId) as? HTMLElement state.element?.clear()
?: error("Element with id 'canvas' not found on page") props.canvasCallback?.invoke(null)
element.clear() }
override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
if (prevProps.obj != props.obj) {
componentDidMount()
}
if (prevProps.selected != props.selected) {
canvas?.select(props.selected)
}
} }
override fun RBuilder.render() { override fun RBuilder.render() {
div { div {
attrs { ref {
id = props.canvasId state.element = findDOMNode(it)
} }
} }
} }
} }
fun RBuilder.threeCanvas(object3D: VisualObject3D, id: String = "threeCanvas", options: Canvas.() -> Unit = {}) { fun RBuilder.threeCanvas(object3D: VisualObject3D, options: Canvas.() -> Unit = {}) {
child(ThreeCanvasComponent::class) { child(ThreeCanvasComponent::class) {
attrs { attrs {
this.obj = object3D this.obj = object3D
this.canvasId = id
this.options = Canvas.invoke(options) this.options = Canvas.invoke(options)
} }
} }

View File

@ -2,6 +2,7 @@ package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Label3D import hep.dataforge.vis.spatial.Label3D
import hep.dataforge.vis.spatial.color import hep.dataforge.vis.spatial.color
import hep.dataforge.vis.spatial.three.ThreeCanvas.Companion.DO_NOT_HIGHLIGHT_TAG
import info.laht.threekt.DoubleSide import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneBufferGeometry import info.laht.threekt.geometries.PlaneBufferGeometry
@ -18,7 +19,7 @@ import kotlin.reflect.KClass
/** /**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html * Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/ */
object ThreeCanvasLabelFactory: ThreeFactory<Label3D> { object ThreeCanvasLabelFactory : ThreeFactory<Label3D> {
override val type: KClass<in Label3D> get() = Label3D::class override val type: KClass<in Label3D> get() = Label3D::class
override fun invoke(obj: Label3D): Object3D { override fun invoke(obj: Label3D): Object3D {
@ -31,7 +32,7 @@ object ThreeCanvasLabelFactory: ThreeFactory<Label3D> {
//canvas.width = metrics.width.toInt() //canvas.width = metrics.width.toInt()
context.fillText(obj.text, (canvas.width - metrics.width)/2, 0.5*canvas.height) context.fillText(obj.text, (canvas.width - metrics.width) / 2, 0.5 * canvas.height)
// canvas contents will be used for a texture // canvas contents will be used for a texture
@ -51,6 +52,7 @@ object ThreeCanvasLabelFactory: ThreeFactory<Label3D> {
mesh.updatePosition(obj) mesh.updatePosition(obj)
mesh.userData[DO_NOT_HIGHLIGHT_TAG] = true
return mesh return mesh
} }
} }

View File

@ -22,11 +22,17 @@ object ThreeMaterials {
color.set(DEFAULT_LINE_COLOR) color.set(DEFAULT_LINE_COLOR)
} }
val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply { val SELECTED_MATERIAL = LineBasicMaterial().apply {
color.set(Colors.ivory) color.set(Colors.ivory)
linewidth = 8.0 linewidth = 8.0
} }
val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply {
color.set(Colors.blue)
linewidth = 8.0
}
fun getLineMaterial(meta: Meta?): LineBasicMaterial { fun getLineMaterial(meta: Meta?): LineBasicMaterial {
if (meta == null) return DEFAULT_LINE if (meta == null) return DEFAULT_LINE
return LineBasicMaterial().apply { return LineBasicMaterial().apply {

View File

@ -1,19 +1,25 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.js.accordion
import hep.dataforge.js.entry
import hep.dataforge.js.requireJS import hep.dataforge.js.requireJS
import hep.dataforge.vis.editor.accordion
import hep.dataforge.vis.spatial.Visual3D import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import kotlinx.html.InputType import kotlinx.html.*
import kotlinx.html.TagConsumer
import kotlinx.html.button
import kotlinx.html.dom.append import kotlinx.html.dom.append
import kotlinx.html.js.* import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import org.w3c.files.Blob import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag import org.w3c.files.BlobPropertyBag
import react.RBuilder
import react.dom.button
import react.dom.div
import react.dom.input
import react.dom.label
import kotlin.dom.clear import kotlin.dom.clear
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) { private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
@ -25,6 +31,70 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
fileSaver.saveAs(blob, fileName) fileSaver.saveAs(blob, fileName)
} }
fun RBuilder.canvasControls(canvas: ThreeCanvas) = accordion("controls") {
entry("Settings") {
div("row") {
div("col-2") {
label("checkbox-inline") {
input(type = InputType.checkBox){
attrs {
defaultChecked = canvas.axes.visible
onChangeFunction = {
canvas.axes.visible = (it.target as HTMLInputElement).checked
}
}
}
+"Axes"
}
}
div("col-1") {
button {
+"Export"
attrs {
onClickFunction = {
val json = (canvas.content as? VisualGroup3D)?.let { group ->
Visual3D.json.stringify(
VisualGroup3D.serializer(),
group
)
}
if (json != null) {
saveData(it, "object.json", "text/json") {
json
}
}
}
}
}
}
}
}
entry("Layers") {
div("row") {
(0..11).forEach { layer ->
div("col-1") {
label { +layer.toString() }
input(type = InputType.checkBox){
attrs {
if (layer == 0) {
defaultChecked = true
}
onChangeFunction = {
if ((it.target as HTMLInputElement).checked) {
canvas.camera.layers.enable(layer)
} else {
canvas.camera.layers.disable(layer)
}
}
}
}
}
}
}
}
}
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) { fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
clear() clear()
append { append {
@ -33,7 +103,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
div("row") { div("row") {
div("col-2") { div("col-2") {
label("checkbox-inline") { label("checkbox-inline") {
input(type = InputType.checkBox).apply { input(type = InputType.checkBox) {
checked = canvas.axes.visible checked = canvas.axes.visible
onChangeFunction = { onChangeFunction = {
canvas.axes.visible = checked canvas.axes.visible = checked
@ -67,7 +137,7 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
(0..11).forEach { layer -> (0..11).forEach { layer ->
div("col-1") { div("col-1") {
label { +layer.toString() } label { +layer.toString() }
input(type = InputType.checkBox).apply { input(type = InputType.checkBox) {
if (layer == 0) { if (layer == 0) {
checked = true checked = true
} }
@ -84,61 +154,6 @@ fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLEl
} }
} }
} }
/* card("Settings") {
div("row") {
div("col-2") {
label("checkbox-inline") {
input(type = InputType.checkBox).apply {
checked = canvas.axes.visible
onChangeFunction = {
canvas.axes.visible = checked
}
}
+"Axes"
}
}
div("col-1") {
button {
+"Export"
onClickFunction = {
val json = (canvas.content as? VisualGroup3D)?.let { group ->
Visual3D.json.stringify(
VisualGroup3D.serializer(),
group
)
}
if (json != null) {
saveData(it, "object.json", "text/json"){
json
}
}
}
}
}
}
}
card("Layers") {
div("row") {
(0..11).forEach { layer ->
div("col-1") {
label { +layer.toString() }
input(type = InputType.checkBox).apply {
if (layer == 0) {
checked = true
}
onChangeFunction = {
if (checked) {
canvas.camera.layers.enable(layer)
} else {
canvas.camera.layers.disable(layer)
}
}
}
}
}
}
}*/
block() block()
} }
} }

View File

@ -153,7 +153,7 @@ open external class Object3D {
* An object that can be used to store custom data about the Object3D. * An object that can be used to store custom data about the Object3D.
* It should not hold references to functions as these will not be cloned. * It should not hold references to functions as these will not be cloned.
*/ */
var userData: Map<String, Any> var userData: dynamic
/** /**
* An optional callback that is executed immediately before the Object3D is rendered. * An optional callback that is executed immediately before the Object3D is rendered.

View File

@ -7,14 +7,11 @@ import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualGroup import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.editor.objectTree import hep.dataforge.vis.editor.renderObjectTree
import hep.dataforge.vis.editor.visualPropertyEditor import hep.dataforge.vis.editor.visualPropertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import hep.dataforge.vis.spatial.gdml.GDMLTransformer import hep.dataforge.vis.spatial.gdml.GDMLTransformer
import hep.dataforge.vis.spatial.gdml.LUnit import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual import hep.dataforge.vis.spatial.gdml.toVisual
@ -160,37 +157,15 @@ private class GDMLDemoApp : Application {
visual is VisualGroup -> visual[name] ?: return visual is VisualGroup -> visual[name] ?: return
else -> return else -> return
} }
canvas.select(name)
editorElement.visualPropertyEditor(name, child) { editorElement.visualPropertyEditor(name, child)
VISIBLE_KEY put true
if (child is VisualObject3D) {
MATERIAL_COLOR_KEY put "#ffffff"
MATERIAL_OPACITY_KEY put 1.0
MATERIAL_WIREFRAME_KEY put false
}
}
// editorElement.displayPropertyEditor(name, child) { item ->
// //val descriptorMeta = Material3D.descriptor
//
// val properties = item.allProperties()
// val bottom = Meta {
// VISIBLE_KEY put (item.visible ?: true)
// if (item is VisualObject3D) {
// MATERIAL_COLOR_KEY put "#ffffff"
// MATERIAL_OPACITY_KEY put 1.0
// MATERIAL_WIREFRAME_KEY put false
// }
// }
// properties.withBottom(bottom)
// }
} }
// canvas.clickListener = ::selectElement // canvas.clickListener = ::selectElement
//tree.visualObjectTree(visual, editor::propertyEditor) //tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(visual) { treeName -> treeElement.renderObjectTree(visual) { treeName ->
selectElement(treeName) selectElement(treeName)
canvas.highlight(treeName)
} }
canvas.render(visual) canvas.render(visual)
message(null) message(null)

View File

@ -72,3 +72,12 @@ application {
//configure<JavaFXOptions> { //configure<JavaFXOptions> {
// modules("javafx.controls") // modules("javafx.controls")
//} //}
val common = project(":dataforge-vis-common")
val copyJsResourcesFromCommon by tasks.creating(Copy::class){
from(common.buildDir.resolve("processedResources\\js\\main\\"))
into(buildDir.resolve("processedResources\\js\\main\\"))
}
tasks.getByPath("jsProcessResources").dependsOn(copyJsResourcesFromCommon)

View File

@ -8,7 +8,7 @@ import ru.mipt.npm.muon.monitor.Monitor.PIXEL_Z_SIZE
/** /**
* A single pixel * A single pixel
*/ */
open class SC1( class SC1(
val name: String, val name: String,
val center: Point3D, val center: Point3D,
val xSize: Double = PIXEL_XY_SIZE, val ySize: Double = PIXEL_XY_SIZE, val zSize: Double = PIXEL_Z_SIZE val xSize: Double = PIXEL_XY_SIZE, val ySize: Double = PIXEL_XY_SIZE, val zSize: Double = PIXEL_Z_SIZE

View File

@ -0,0 +1,138 @@
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.isEmpty
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
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 kotlin.math.PI
interface MMAppProps : RProps {
var model: Model
var context: Context
}
interface MMAppState : RState {
var model: Model
var selected: Name?
var canvas: ThreeCanvas?
}
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 = {
setState {
selected = it
}
}
private val canvasConfig = Canvas {
camera = Camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
}
override fun RBuilder.render() {
val visual = model.root
val selected = state.selected
div("row") {
div("col-lg-3") {
//tree
card("Object tree") {
objectTree(visual, selected, onSelect)
}
}
div("col-lg-6") {
//canvas
child(ThreeCanvasComponent::class) {
attrs {
this.context = props.context
this.obj = visual
this.options = canvasConfig
this.selected = selected
this.clickCallback = onSelect
this.canvasCallback = {
setState{
canvas = it
}
}
}
}
}
div("col-lg-3") {
div("row") {
//settings
state.canvas?.let { canvasControls(it) }
card("Events") {
button {
+"Next"
attrs {
onClickFunction = {
GlobalScope.launch {
val event = connection.get<Event>("http://localhost:8080/event")
model.displayEvent(event)
}
}
}
}
button {
+"Clear"
attrs {
onClickFunction = {
model.reset()
}
}
}
}
}
div("row") {
//properties
if (selected != null) {
val selectedObject = when {
selected.isEmpty() -> visual
else -> visual[selected]
}
if (selectedObject != null) {
//TODO replace by explicit breadcrumbs with callback
visualPropertyEditor(selected, selectedObject, descriptor = VisualObject3D.descriptor)
}
}
}
}
}
}
}

View File

@ -3,31 +3,13 @@ package ru.mipt.npm.muon.monitor
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.js.Application import hep.dataforge.js.Application
import hep.dataforge.js.startApplication import hep.dataforge.js.startApplication
import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.editor.card
import hep.dataforge.vis.editor.objectTree
import hep.dataforge.vis.editor.visualPropertyEditor
import hep.dataforge.vis.spatial.Visual3D import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.displayCanvasControls
import hep.dataforge.vis.spatial.three.output
import info.laht.threekt.math.Vector3
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer 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.button
import kotlinx.html.js.onClickFunction
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.w3c.dom.HTMLElement import react.dom.render
import kotlin.browser.document import kotlin.browser.document
import kotlin.dom.clear
private class MMDemoApp : Application { private class MMDemoApp : Application {
@ -42,64 +24,80 @@ private class MMDemoApp : 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 three = context.plugins.load(ThreePlugin)
//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 element = document.getElementById("app") ?: error("Element with id 'app' 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() render(element) {
val visual: VisualObject3D = model.root child(MMAppComponent::class) {
attrs {
//output.camera.layers.enable(1) this.model = model
val canvas = three.output(canvasElement as HTMLElement) this.context = context
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()
}
} }
} }
} }
// //val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
//
fun selectElement(name: Name) { // val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
val child: VisualObject = when { // val settingsElement = document.getElementById("settings")
name.isEmpty() -> visual // ?: error("Element with id 'settings' not found on page")
visual is VisualGroup -> visual[name] ?: return // val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
else -> return // val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
} //
editorElement.visualPropertyEditor(name, child, descriptor = VisualObject3D.descriptor) // canvasElement.clear()
// val visual = model.root
} //
// //output.camera.layers.enable(1)
// canvas.clickListener = ::selectElement // val canvas = three.output(canvasElement as HTMLElement)
//
//tree.visualObjectTree(visual, editor::propertyEditor) // canvas.camera.layers.set(0)
treeElement.objectTree(visual) { name -> // canvas.camera.position.z = -2000.0
selectElement(name) // canvas.camera.position.y = 500.0
canvas.highlight(name) // canvas.camera.lookAt(Vector3(0, 0, 0))
} //
canvas.render(visual) // 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)
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,748 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="240"
height="144"
id="svg4136"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="jsoneditor-icons.svg">
<title
id="title6512">JSON Editor Icons</title>
<metadata
id="metadata4148">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>JSON Editor Icons</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4146" />
<sodipodi:namedview
pagecolor="#ff63ff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1026"
id="namedview4144"
showgrid="true"
inkscape:zoom="4"
inkscape:cx="13.229181"
inkscape:cy="119.82429"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4136"
showguides="false"
borderlayer="false"
inkscape:showpageshadow="true"
showborder="true">
<inkscape:grid
type="xygrid"
id="grid4640"
empspacing="24" />
</sodipodi:namedview>
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1"
height="16"
width="16"
y="4"
x="4" />
<rect
id="svg_1-7"
height="16"
width="16"
y="3.999995"
x="28.000006"
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
x="52.000004"
y="3.999995"
width="16"
height="16"
id="rect4165" />
<rect
id="rect4175"
height="16"
width="16"
y="3.9999852"
x="172.00002"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
id="rect4175-3"
height="16"
width="16"
y="3.999995"
x="196"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<g
id="g4299"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<g
id="g4299-3"
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1-0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1-9"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<rect
id="svg_1-7-5"
height="6.9999905"
width="6.9999909"
y="7.0000048"
x="55.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="58"
y="10.00001"
width="6.9999909"
height="6.9999905"
id="rect4354" />
<rect
id="svg_1-7-5-7"
height="6.9999905"
width="6.9999909"
y="10.000005"
x="58.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<g
id="g4378">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="10.999999"
width="7.9999909"
height="1.9999965"
id="svg_1-7-5-3" />
<rect
id="rect4374"
height="1.9999946"
width="11.999995"
y="7.0000005"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
id="rect4376"
height="1.9999995"
width="3.9999928"
y="14.999996"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
</g>
<g
transform="matrix(1,0,0,-1,-23.999995,23.999995)"
id="g4383">
<rect
id="rect4385"
height="1.9999965"
width="7.9999909"
y="10.999999"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="7.0000005"
width="11.999995"
height="1.9999946"
id="rect4387" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="14.999996"
width="3.9999928"
height="1.9999995"
id="rect4389" />
</g>
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-4"
width="16"
height="16"
x="76"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
id="path4351"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
id="path4351-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-25"
width="16"
height="16"
x="100"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
id="path2987"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
id="path2987-1"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-73"
width="16"
height="16"
x="124"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
id="path3780"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
id="path3782"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-35"
width="16"
height="16"
x="148"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<rect
id="svg_1-7-2"
height="1.9999961"
width="11.999996"
y="64"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="svg_1-7-2-2"
height="2.9999905"
width="2.9999907"
y="52"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="52"
width="2.9999907"
height="2.9999905"
id="rect4561" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="80.000008"
y="58"
width="2.9999907"
height="2.9999905"
id="rect4563" />
<rect
id="rect4565"
height="2.9999905"
width="2.9999907"
y="58"
x="85.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="rect4567"
height="2.9999905"
width="2.9999907"
y="64"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="64"
width="2.9999907"
height="2.9999905"
id="rect4569" />
<circle
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4571"
cx="110.06081"
cy="57.939209"
r="4.7438836" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="116.64566"
y="-31.79752"
width="4.229713"
height="6.4053884"
id="rect4563-2"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
<path
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 125,56 138.77027,56.095 132,64 Z"
id="path4613"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615"
d="M 149,64 162.77027,63.905 156,56 Z"
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="54"
y="53"
width="11.999996"
height="1.9999961"
id="rect4638" />
<rect
id="svg_1-7-2-24"
height="1.9999957"
width="12.99999"
y="-56"
x="53"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
transform="matrix(0,1,-1,0,0,0)" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="53"
y="-66"
width="12.99999"
height="1.9999957"
id="rect4657" />
<rect
id="rect4659"
height="0.99999291"
width="11.999999"
y="57"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="88.000122"
width="11.999996"
height="1.9999961"
id="rect4661" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="76.000122"
width="2.9999907"
height="2.9999905"
id="rect4663" />
<rect
id="rect4665"
height="2.9999905"
width="2.9999907"
y="76.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
id="rect4667"
height="2.9999905"
width="2.9999907"
y="82.000122"
x="80.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="85.000008"
y="82.000122"
width="2.9999907"
height="2.9999905"
id="rect4669" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="88.000122"
width="2.9999907"
height="2.9999905"
id="rect4671" />
<rect
id="rect4673"
height="2.9999905"
width="2.9999907"
y="88.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<circle
r="4.7438836"
cy="81.939331"
cx="110.06081"
id="circle4675"
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
id="rect4677"
height="6.4053884"
width="4.229713"
y="-14.826816"
x="133.6163"
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4679"
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
id="path4681"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<rect
id="rect4683"
height="1.9999961"
width="11.999996"
y="77.000122"
x="54"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="77.000122"
y="-56"
width="12.99999"
height="1.9999957"
id="rect4685" />
<rect
id="rect4687"
height="1.9999957"
width="12.99999"
y="-66"
x="77.000122"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
transform="matrix(0,1,-1,0,0,0)" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="81.000122"
width="11.999999"
height="0.99999291"
id="rect4689" />
<rect
id="rect4761-1"
height="1.9999945"
width="15.99999"
y="101"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-0"
height="1.9999945"
width="15.99999"
y="105"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-7"
height="1.9999945"
width="9"
y="109"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1"
height="1.9999945"
width="12"
y="125"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4"
height="1.9999945"
width="10"
y="137"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4"
height="1.9999945"
width="10"
y="129"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4-3"
height="1.9999945"
width="9"
y="133"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
id="path3055-0-77" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9850574,108.015 14.0298856,-0.03"
id="path5244-5-0-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.96599996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9849874,132.015 14.0298866,-0.03"
id="path5244-5-0-5-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138-12" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1-3" />
<path
id="path6191"
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
id="path6193" />
<path
id="path6195"
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.96599996;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4500"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073242"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
inkscape:transform-center-x="-1.2779026" />
<path
inkscape:transform-center-x="1.277902"
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073242"
sodipodi:cx="-36.611614"
sodipodi:sides="3"
id="path4502"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="scale(-1,1)" />
<path
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073212"
sodipodi:cx="11.55581"
sodipodi:sides="3"
id="path4504"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="matrix(0,1,-1,0,72.0074,71.7877)"
inkscape:transform-center-y="1.2779029" />
<path
inkscape:transform-center-y="-1.2779026"
transform="matrix(0,-1,-1,0,96,96)"
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4506"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073212"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615-5"
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,55 0,6 2,0 0,-6"
id="path4300"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,62 0,2 2,0 0,-2"
id="path4300-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.8"
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
id="path4268"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccc" />
<rect
id="rect4175-3-5"
height="16"
width="16"
y="4"
x="220"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<path
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 234,6 0,2 -5,5 0,5 -2,0 0,-5 -5,-5 0,-2"
id="path3546"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<g
transform="matrix(1.3333328,0,0,-1.5999992,-139.9999,127.19999)"
id="g4383-6">
<rect
id="rect4385-2"
height="1.2499905"
width="5.9999924"
y="12.625005"
x="198.00002"
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="15.125007"
width="7.4999928"
height="1.2499949"
id="rect4387-9" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="7.6250024"
width="2.9999909"
height="1.2499905"
id="rect4389-1-0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="10.125004"
width="4.4999919"
height="1.2499905"
id="rect4389-1-9" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 207.00001,16.375004 0,-5.625005 -2.25,0 3,-3.1250014 3,3.1250014 -2.25,0 0,5.625005 -1.5,0"
id="path4402"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 164,100 0,3 -6,6 0,7 -4,0 0,-7 -6,-6 0,-3"
id="path3546-2-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-3"
height="16"
width="16"
y="28"
x="4" />
<path
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0"
id="path4402-5-7"
d="m 15,41 0,-7 -4,0 0,3 -5,-4 5,-4 0,3 6,0 0,9"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -7,6 +7,8 @@
<link rel="stylesheet" href="css/bootstrap.min.css"> <link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/main.css"> <link rel="stylesheet" href="css/main.css">
<script type="text/javascript" src="main.bundle.js"></script> <script type="text/javascript" src="main.bundle.js"></script>
<script type="text/javascript" src ="js/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src ="js/bootstrap.bundle.min.js"></script>
</head> </head>
<body class="testApp"> <body class="testApp">
@ -15,7 +17,8 @@
</div> </div>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div id="app"></div>
<!-- <div class="row">
<div class="col-lg-3"> <div class="col-lg-3">
<div class="row" id="tree"></div> <div class="row" id="tree"></div>
</div> </div>
@ -26,7 +29,7 @@
<div class="row" id="settings"></div> <div class="row" id="settings"></div>
<div class="row" id="editor"></div> <div class="row" id="editor"></div>
</div> </div>
</div> </div>-->
</div> </div>
</body> </body>
</html> </html>