A lot of UI refactoring

This commit is contained in:
Alexander Nozik 2020-05-16 19:26:28 +03:00
parent 49b010a387
commit 484423a17a
78 changed files with 1955 additions and 1302 deletions

View File

@ -1,15 +1,11 @@
import scientifik.fx
import scientifik.serialization
val dataforgeVersion by extra("0.1.7")
val dataforgeVersion by extra("0.1.8-dev-2")
plugins {
val toolsVersion = "0.4.2"
val toolsVersion = "0.5.0"
id("scientifik.mpp") version toolsVersion apply false
id("scientifik.jvm") version toolsVersion apply false
id("scientifik.js") version toolsVersion apply false
id("scientifik.publish") version toolsVersion apply false
id("org.openjfx.javafxplugin") version "0.0.8" apply false
}
allprojects {
@ -19,12 +15,13 @@ allprojects {
maven("http://maven.jzy3d.org/releases")
maven("https://kotlin.bintray.com/js-externals")
maven("https://kotlin.bintray.com/kotlin-js-wrappers/")
maven("https://dl.bintray.com/mipt-npm/dataforge")
// maven("https://dl.bintray.com/gbaldeck/kotlin")
// maven("https://dl.bintray.com/rjaros/kotlin")
}
group = "hep.dataforge"
version = "0.1.3-dev"
version = "0.1.4-dev"
}
val githubProject by extra("dataforge-vis")
@ -32,9 +29,9 @@ val bintrayRepo by extra("dataforge")
val fxVersion by extra("14")
subprojects {
apply(plugin = "scientifik.publish")
serialization()
afterEvaluate {
fx(scientifik.FXModule.CONTROLS, version = fxVersion)
if(name.startsWith("dataforge")) {
apply(plugin = "scientifik.publish")
}
useSerialization()
useFx(FXModule.CONTROLS, version = fxVersion)
}

View File

@ -32,19 +32,23 @@ kotlin {
dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
//React, React DOM + Wrappers (chapter 3)
api("org.jetbrains:kotlin-react:16.13.0-pre.94-kotlin-1.3.70")
api("org.jetbrains:kotlin-react-dom:16.13.0-pre.94-kotlin-1.3.70")
api(npm("react", "16.13.0"))
api(npm("react-dom", "16.13.0"))
api("org.jetbrains:kotlin-react:16.13.1-pre.104-kotlin-1.3.72")
api("org.jetbrains:kotlin-react-dom:16.13.1-pre.104-kotlin-1.3.72")
api("org.jetbrains.kotlinx:kotlinx-html:0.6.12")
//Kotlin Styled (chapter 3)
api("org.jetbrains:kotlin-styled:1.0.0-pre.94-kotlin-1.3.70")
api(npm("styled-components"))
api(npm("inline-style-prefixer"))
api("org.jetbrains:kotlin-extensions:1.0.1-pre.104-kotlin-1.3.72")
api("org.jetbrains:kotlin-css-js:1.0.0-pre.94-kotlin-1.3.70")
api("org.jetbrains:kotlin-styled:1.0.0-pre.104-kotlin-1.3.72")
api(npm("source-map-resolve","0.6.0"))
api(npm("file-saver","2.0.2"))
api(npm("core-js", "2.6.5"))
api(npm("react", "16.13.1"))
api(npm("react-dom", "16.13.1"))
api(npm("react-is", "16.13.0"))
api(npm("inline-style-prefixer", "5.1.0"))
api(npm("styled-components", "4.3.2"))
//api(project(":ringui-wrapper"))
}
}
}

View File

@ -83,20 +83,34 @@ abstract class AbstractVisualObject : VisualObject {
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
yield(parent?.getProperty(name, inherit))
}.merge()
} else {
properties?.get(name) ?: mergedStyles[name]
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
}.merge()
}
}
/**
* Reset all properties to their default values
*/
fun resetProperties() {
properties?.removeListener(this)
properties = null
}
companion object {
val descriptor = NodeDescriptor {
defineValue(STYLE_KEY){
value(STYLE_KEY) {
type(ValueType.STRING)
multiple = true
}
}
}
}

View File

@ -44,6 +44,9 @@ interface VisualObject : Configurable {
*/
fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit
/**
* Send a signal that property value should be reevaluated
*/
fun propertyInvalidated(name: Name) = propertyChanged(name, null, null)
/**

View File

@ -1,5 +1,8 @@
package hep.dataforge.vis
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.node
import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
@ -17,3 +20,15 @@ tailrec fun Name.selectable(): Name? = when {
cutLast().selectable()
}
}
fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>?{
return when (val first = filterNotNull().firstOrNull()) {
null -> null
is MetaItem.ValueItem -> first //fast search for first entry if it is value
is MetaItem.NodeItem -> {
//merge nodes if first encountered node is meta
val laminate: Laminate = Laminate(mapNotNull { it.node }.toList())
MetaItem.NodeItem(laminate)
}
}
}

View File

@ -2,22 +2,25 @@ package hep.dataforge.vis
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.descriptors.attributes
import hep.dataforge.meta.descriptors.setAttribute
import hep.dataforge.names.toName
import hep.dataforge.values.asValue
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
var ValueDescriptor.widget: Meta
get() = getProperty("widget").node ?: Meta.EMPTY
get() = attributes["widget"].node ?: Meta.EMPTY
set(value) {
setProperty("widget", value)
setAttribute("widget".toName(), value)
}
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
var ValueDescriptor.widgetType: String?
get() = getProperty("widget.type").string
get() = attributes["widget.type"].string
set(value) {
setProperty("widget.type", value?.asValue())
setAttribute("widget.type".toName(), value)
}

View File

@ -12,10 +12,6 @@ inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
inline fun js(builder: dynamic.() -> Unit): dynamic = jsObject(builder)
//fun <T : Any> clone(obj: T) = objectAssign(jsObject<T> {}, obj)
//inline fun <T : Any> assign(obj: T, builder: T.() -> Unit) = clone(obj).apply(builder)
fun toPlainObjectStripNull(obj: Any) = js {
for (key in Object.keys(obj)) {
val value = obj.asDynamic()[key]

View File

@ -1,90 +0,0 @@
package hep.dataforge.vis.editor
import hep.dataforge.meta.Config
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get
import hep.dataforge.meta.setValue
import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.values.*
import hep.dataforge.vis.widgetType
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event
import react.RBuilder
import react.dom.*
internal fun RBuilder.valueChooser(root: Config, name: Name, value: Value, descriptor: ValueDescriptor?) {
val onValueChange: (Event) -> Unit = {
val res = when (val t = it.target) {
// (it.target as HTMLInputElement).value
is HTMLInputElement -> if (t.type == "checkbox") {
if (t.checked) True else False
} else {
t.value.asValue()
}
is HTMLSelectElement -> t.value.asValue()
else -> error("Unknown event target: $t")
}
try {
root.setValue(name, res)
} catch (ex: Exception) {
console.error("Can't set config property ${name} to $res")
}
}
div() {
val type = descriptor?.type?.firstOrNull()
when {
type == ValueType.BOOLEAN -> {
input(type = InputType.checkBox) {
attrs {
checked = value.boolean
onChangeFunction = onValueChange
}
}
}
type == ValueType.NUMBER -> input(type = InputType.number, classes = "form-control w-100") {
attrs {
descriptor.attributes["step"].string?.let {
step = it
}
descriptor.attributes["min"].string?.let {
min = it
}
descriptor.attributes["max"].string?.let {
max = it
}
this.defaultValue = value.string
onChangeFunction = onValueChange
}
}
descriptor?.allowedValues?.isNotEmpty() ?: false -> select (classes = "w-100") {
descriptor!!.allowedValues.forEach {
option {
+it.string
}
}
attrs {
multiple = false
onChangeFunction = onValueChange
}
}
descriptor?.widgetType == "color" -> input(type = InputType.color) {
attrs {
this.value = value.string
onChangeFunction = onValueChange
}
}
else -> input(type = InputType.text, classes = "form-control w-100") {
attrs {
this.value = value.string
onChangeFunction = onValueChange
}
}
}
}
}

View File

@ -26,21 +26,21 @@ class MetaEditorDemo : View("Meta editor demo") {
}.asConfig()
val descriptor = NodeDescriptor {
defineNode("aNode") {
node("aNode") {
info = "A root demo node"
defineValue("b") {
value("b") {
info = "b number value"
type(ValueType.NUMBER)
}
defineNode("otherNode") {
defineValue("otherValue") {
node("otherNode") {
value("otherValue") {
type(ValueType.BOOLEAN)
default(false)
info = "default value"
}
}
}
defineValue("multiple") {
node("multiple") {
info = "A sns value"
multiple = true
}

View File

@ -253,9 +253,7 @@ private fun volume(
}
fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
val context = GDMLTransformer(this).apply(block)
return context.finalize(volume(context, world))
}

View File

@ -1,10 +1,10 @@
import scientifik.serialization
import scientifik.*
plugins {
id("scientifik.mpp")
}
serialization()
useSerialization()
kotlin {
js {
@ -30,9 +30,10 @@ kotlin {
}
jsMain {
dependencies {
// api(project(":wrappers"))
implementation(project(":ui:bootstrap"))//to be removed later
implementation(npm("three", "0.114.0"))
implementation(npm("three-csg-ts", "1.0.1"))
api(npm("file-saver", "2.0.2"))
}
}
}

View File

@ -2,6 +2,7 @@ package hep.dataforge.vis.spatial
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.attributes
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.values.ValueType
@ -39,7 +40,8 @@ class Material3D : Scheme() {
val MATERIAL_KEY = "material".asName()
internal val COLOR_KEY = "color".asName()
val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY
val SPECULAR_COLOR_KEY = "specularColor".asName()
internal val SPECULAR_COLOR_KEY = "specularColor".asName()
val MATERIAL_SPECULAR_COLOR_KEY = MATERIAL_KEY + SPECULAR_COLOR_KEY
internal val OPACITY_KEY = "opacity".asName()
val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY
internal val WIREFRAME_KEY = "wireframe".asName()
@ -48,21 +50,22 @@ class Material3D : Scheme() {
val descriptor by lazy {
//must be lazy to avoid initialization bug
NodeDescriptor {
defineValue(COLOR_KEY) {
value(COLOR_KEY) {
type(ValueType.STRING, ValueType.NUMBER)
default("#ffffff")
widgetType = "color"
}
defineValue(OPACITY_KEY) {
value(OPACITY_KEY) {
type(ValueType.NUMBER)
default(1.0)
config["attributes"] = Meta {
attributes {
this["min"] = 0.0
this["max"] = 1.0
this["step"] = 0.1
}
widgetType = "slider"
}
defineValue(WIREFRAME_KEY) {
value(WIREFRAME_KEY) {
type(ValueType.BOOLEAN)
default(false)
}

View File

@ -50,14 +50,18 @@ class Proxy private constructor(
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name)
?: parent?.getProperty(name)
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
yield(prototype.getProperty(name))
yield(parent?.getProperty(name, inherit))
}.merge()
} else {
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
yield(prototype.getProperty(name, false))
}.merge()
}
}
@ -125,14 +129,18 @@ class Proxy private constructor(
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name)
?: parent?.getProperty(name)
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
yield(prototype.getProperty(name))
yield(parent?.getProperty(name, inherit))
}.merge()
} else {
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
sequence {
yield(properties?.get(name))
yield(mergedStyles[name])
yield(prototype.getProperty(name, false))
}.merge()
}
}

View File

@ -63,18 +63,18 @@ interface VisualObject3D : VisualObject {
val descriptor by lazy {
NodeDescriptor {
defineValue(VISIBLE_KEY) {
value(VISIBLE_KEY) {
type(ValueType.BOOLEAN)
default(true)
}
//TODO replace by descriptor merge
defineValue(VisualObject.STYLE_KEY){
value(VisualObject.STYLE_KEY){
type(ValueType.STRING)
multiple = true
}
defineItem(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
item(Material3D.MATERIAL_KEY.toString(), Material3D.descriptor)
}
}
}

View File

@ -96,7 +96,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
val picked = pick()
if (picked != null && this.picked != picked) {
this.picked?.toggleHighlight(false,HIGHLIGHT_NAME, HIGHLIGHT_MATERIAL)
this.picked?.toggleHighlight(false, HIGHLIGHT_NAME, HIGHLIGHT_MATERIAL)
picked.toggleHighlight(true, HIGHLIGHT_NAME, HIGHLIGHT_MATERIAL)
this.picked = picked
}
@ -131,10 +131,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
}
}
private fun Object3D.isStatic(): Boolean {
return false
}
//find first non-static parent in this object ancestry
private fun Object3D?.upTrace(): Object3D? = if (this?.name?.startsWith("@") == true) parent else this
private fun pick(): Object3D? {
@ -144,7 +141,8 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
// 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() }
//skip invisible objects
val obj = intersects.map { it.`object` }.firstOrNull { it.visible }
obj.upTrace()
}
}
@ -168,7 +166,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
}
}
fun clear(){
fun clear() {
scene.children.find { it.name == "@root" }?.let {
scene.remove(it)
}
@ -186,7 +184,7 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
root = object3D
}
private var highlighted: Object3D? = null
private var selected: Object3D? = null
/**
* Toggle highlight for the given [Mesh] object
@ -224,15 +222,15 @@ class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val canvas: Canv
*/
fun select(name: Name?) {
if (name == null) {
highlighted?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
highlighted = null
selected?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
selected = null
return
}
val obj = root?.findChild(name)
if (obj != null && highlighted != obj) {
highlighted?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
if (obj != null && selected != obj) {
selected?.toggleHighlight(false, SELECT_NAME, SELECTED_MATERIAL)
obj.toggleHighlight(true, SELECT_NAME, SELECTED_MATERIAL)
highlighted = obj
selected = obj
}
}

View File

@ -29,7 +29,7 @@ interface ThreeCanvasState : RState {
class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
var canvas: ThreeCanvas? = null
private var canvas: ThreeCanvas? = null
override fun componentDidMount() {
if(canvas == null) {

View File

@ -5,6 +5,7 @@ import hep.dataforge.values.ValueType
import hep.dataforge.vis.Colors
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_SPECULAR_COLOR_KEY
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial

View File

@ -1,8 +1,8 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.js.accordion
import hep.dataforge.js.entry
import hep.dataforge.js.requireJS
import hep.dataforge.vis.bootstrap.accordion
import hep.dataforge.vis.bootstrap.entry
import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualGroup3D
import kotlinx.html.*

View File

@ -1,6 +1,4 @@
import scientifik.DependencyConfiguration
import scientifik.FXModule
import scientifik.fx
import scientifik.*
plugins {
id("scientifik.mpp")
@ -8,7 +6,7 @@ plugins {
}
val fxVersion: String by rootProject.extra
fx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
useFx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
kotlin {
@ -17,6 +15,7 @@ kotlin {
}
js {
useCommonJs()
browser {
webpackTask {
//sourceMaps = false
@ -27,8 +26,14 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api(project(":dataforge-vis-spatial"))
api(project(":dataforge-vis-spatial-gdml"))
implementation(project(":dataforge-vis-spatial"))
implementation(project(":dataforge-vis-spatial-gdml"))
}
}
jsMain{
dependencies {
implementation(project(":ui:bootstrap"))
implementation(npm("react-file-drop", "3.0.6"))
}
}
}

View File

@ -0,0 +1,40 @@
@file:JsModule("react-file-drop")
@file:JsNonModule
package drop
import org.w3c.dom.DragEvent
import org.w3c.files.FileList
import react.*
external enum class DropEffects {
copy,
move,
link,
none
}
external interface FileDropProps: RProps {
var className: String?
var targetClassName: String?
var draggingOverFrameClassName: String?
var draggingOverTargetClassName: String?
// var frame?: Exclude<HTMLElementTagNameMap[keyof HTMLElementTagNameMap], HTMLElement> | HTMLDocument;
var onFrameDragEnter: ((event: DragEvent) -> Unit)?
var onFrameDragLeave: ((event: DragEvent) -> Unit)?
var onFrameDrop: ((event: DragEvent) -> Unit)?
// var onDragOver: ReactDragEventHandler<HTMLDivElement>?
// var onDragLeave: ReactDragEventHandler<HTMLDivElement>?
var onDrop: ((files: FileList?, event: dynamic) -> Unit)?//event:DragEvent<HTMLDivElement>)
var dropEffect: DropEffects?
}
external interface FileDropState: RState {
var draggingOverFrame: Boolean
var draggingOverTarget: Boolean
}
external class FileDrop : Component<FileDropProps, FileDropState> {
override fun render(): dynamic
}

View File

@ -0,0 +1,138 @@
package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Context
import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.bootstrap.*
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.gdml.toVisual
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 org.w3c.files.FileReader
import org.w3c.files.get
import react.RProps
import react.dom.h1
import scientifik.gdml.GDML
import scientifik.gdml.parse
import kotlin.browser.window
import kotlin.math.PI
interface GDMLAppProps : RProps {
var context: Context
var rootObject: VisualObject?
var selected: Name?
}
private val canvasConfig = Canvas {
camera = Camera {
distance = 2100.0
latitude = PI / 6
azimuth = PI + PI / 6
}
}
val GDMLApp = component<GDMLAppProps> { props ->
var selected by state { props.selected }
var canvas: ThreeCanvas? by state { null }
var visual: VisualObject? by state { props.rootObject }
val select: (Name?) -> Unit = {
selected = it
}
fun loadData(name: String, data: String) {
visual = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> {
val gdml = GDML.parse(data)
gdml.toVisual(gdmlConfiguration)
}
name.endsWith(".json") -> VisualGroup3D.parseJson(data)
else -> {
window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name")
}
}
}
flexColumn {
h1 { +"GDML/JSON loader demo" }
gridRow {
gridColumn(3) {
card("Load data") {
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
if (file != null) {
FileReader().apply {
onload = {
val string = result as String
loadData(file.name, string)
}
readAsText(file)
}
}
}
}
//tree
card("Object tree") {
visual?.let {
objectTree(it, selected, select)
}
}
}
gridColumn(6) {
//canvas
(visual as? VisualObject3D)?.let { visual3D ->
child(ThreeCanvasComponent::class) {
attrs {
this.context = props.context
this.obj = visual3D
this.selected = selected
this.clickCallback = select
this.canvasCallback = {
canvas = it
}
}
}
}
}
gridColumn(3) {
gridRow {
//settings
canvas?.let {
card("Canvas configuration") {
canvasControls(it)
}
}
}
gridRow {
namecrumbs(selected, "World") { selected = it }
}
gridRow {
//properties
card("Properties") {
selected.let { selected ->
val selectedObject: VisualObject? = when {
selected == null -> null
selected.isEmpty() -> visual
else -> (visual as? VisualGroup)?.get(selected)
}
if (selectedObject != null) {
configEditor(selectedObject, default = selectedObject.allProperties(), key = selected)
}
}
}
}
}
}
}
}

View File

@ -3,193 +3,72 @@ package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Global
import hep.dataforge.js.Application
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.renderObjectTree
import hep.dataforge.vis.editor.visualPropertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D
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.displayCanvasControls
import hep.dataforge.vis.spatial.three.output
import org.w3c.dom.*
import org.w3c.files.FileList
import org.w3c.files.FileReader
import org.w3c.files.get
import scientifik.gdml.GDML
import react.child
import react.dom.div
import react.dom.render
import kotlin.browser.document
import kotlin.browser.window
import kotlin.dom.clear
val gdmlConfiguration: GDMLTransformer.() -> Unit = {
lUnit = LUnit.CM
volumeAction = { volume ->
when {
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT
else -> GDMLTransformer.Action.CACHE
}
}
solidConfiguration = { parent, solid ->
if (
solid.name.startsWith("Yoke")
|| solid.name.startsWith("Pole")
|| parent.physVolumes.isNotEmpty()
) {
useStyle("opaque") {
MATERIAL_OPACITY_KEY put 0.3
}
}
}
}
private class GDMLDemoApp : Application {
/**
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
*/
private fun handleDragOver(event: DragEvent) {
event.stopPropagation()
event.preventDefault()
event.dataTransfer?.dropEffect = "copy"
}
/**
* Load data from text file
*/
private fun loadData(event: DragEvent, block: (name: String, data: String) -> Unit) {
event.stopPropagation()
event.preventDefault()
val file = (event.dataTransfer?.files as FileList)[0]
?: throw RuntimeException("Failed to load file")
FileReader().apply {
onload = {
val string = result as String
block(file.name, string)
}
readAsText(file)
}
}
private fun spinner(show: Boolean) {
// if( show){
//
// val style = if (show) {
// "display:block;"
// } else {
// "display:none;"
// }
// document.getElementById("canvas")?.append {
//
// }
}
private fun message(message: String?) {
console.log(message)
// document.getElementById("messages")?.let { element ->
// if (message == null) {
// element.clear()
// } else {
// element.append {
// p {
// +message
// }
// }
// }
// }
}
private val gdmlConfiguration: GDMLTransformer.() -> Unit = {
lUnit = LUnit.CM
volumeAction = { volume ->
when {
volume.name.startsWith("ecal01lay") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("UPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("USCL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("VPBL") -> GDMLTransformer.Action.REJECT
volume.name.startsWith("VSCL") -> GDMLTransformer.Action.REJECT
else -> GDMLTransformer.Action.CACHE
}
}
solidConfiguration = { parent, solid ->
if (
solid.name.startsWith("Yoke")
|| solid.name.startsWith("Pole")
|| parent.physVolumes.isNotEmpty()
) {
useStyle("opaque") {
MATERIAL_OPACITY_KEY put 0.3
}
}
}
}
override fun start(state: Map<String, Any>) {
val context = Global.context("demo") {}
val three = context.plugins.load(ThreePlugin)
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
val configElement = document.getElementById("config") ?: error("Element with id 'layers' not found on page")
val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
canvasElement.clear()
val action: (name: String, data: String) -> Unit = { name, data ->
canvasElement.clear()
spinner(true)
val visual: VisualObject3D = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> {
message("Loading GDML")
val gdml = GDML.format.parse(GDML.serializer(), data)
message("Converting GDML into DF-VIS format")
gdml.toVisual(gdmlConfiguration)
}
name.endsWith(".json") -> VisualGroup3D.parseJson(data)
else -> {
window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name")
}
}
//Optimize tree
//(visual as? VisualGroup3D)?.transformInPlace(UnRef, RemoveSingleChild)
message("Rendering")
//output.camera.layers.enable(1)
val canvas = three.output(canvasElement as HTMLElement)
canvas.camera.layers.set(0)
configElement.displayCanvasControls(canvas)
//tree.visualObjectTree(visual, editor::propertyEditor)
fun selectElement(name: Name) {
val child: VisualObject = when {
name.isEmpty() -> visual
visual is VisualGroup -> visual[name] ?: return
else -> return
}
canvas.select(name)
editorElement.visualPropertyEditor(name, child)
}
// canvas.clickListener = ::selectElement
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.renderObjectTree(visual) { treeName ->
selectElement(treeName)
}
canvas.render(visual)
message(null)
spinner(false)
}
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {
addEventListener("dragover", { handleDragOver(it as DragEvent) }, false)
addEventListener("drop", { loadData(it as DragEvent, action) }, false)
}
(document.getElementById("file_load_button") as? HTMLInputElement)?.apply {
addEventListener("change", {
(it.target as HTMLInputElement).files?.asList()?.first()?.let { file ->
FileReader().apply {
onload = {
val string = result as String
action(file.name, string)
}
readAsText(file)
render(element) {
div("h-100") {
child(GDMLApp) {
attrs {
this.context = context
this.rootObject = cubes().toVisual(gdmlConfiguration)
}
}
}, false)
}
}
// (document.getElementById("file_load_button") as? HTMLInputElement)?.apply {
// addEventListener("change", {
// (it.target as HTMLInputElement).files?.asList()?.first()?.let { file ->
// FileReader().apply {
// onload = {
// val string = result as String
// action(file.name, string)
// }
// readAsText(file)
// }
// }
// }, false)
// }
}
}

View File

@ -0,0 +1,30 @@
package hep.dataforge.vis.spatial.gdml.demo
import drop.FileDrop
import kotlinx.css.*
import kotlinx.css.properties.border
import org.w3c.files.FileList
import react.RBuilder
import styled.css
import styled.styledDiv
//TODO move styles to inline
fun RBuilder.fileDrop(title: String, action: (files: FileList?) -> Unit) {
styledDiv {
css {
border(style = BorderStyle.dashed, width = 1.px, color = Color.orange)
alignContent = Align.center
}
child(FileDrop::class) {
attrs {
onDrop = { files, _ ->
console.info("loaded $files")
action(files)
}
}
+title
}
}
}

View File

@ -0,0 +1,62 @@
.file-drop {
/* relatively position the container bc the contents are absolute */
position: relative;
height: 100px;
width: 100%;
}
.file-drop > .file-drop-target {
/* basic styles */
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
border-radius: 2px;
/* horizontally and vertically center all content */
display: flex;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
align-items: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
justify-content: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
align-content: center;
-webkit-align-content: center;
-ms-flex-line-pack: center;
text-align: center;
}
.file-drop > .file-drop-target.file-drop-dragging-over-frame {
/* overlay a black mask when dragging over the frame */
border: none;
background-color: rgba(0, 0, 0, 0.65);
box-shadow: none;
z-index: 50;
opacity: 1;
/* typography */
color: white;
}
.file-drop > .file-drop-target.file-drop-dragging-over-target {
/* turn stuff orange when we are dragging over the target */
color: #ff6e40;
box-shadow: 0 0 13px 3px #ff6e40;
}

File diff suppressed because one or more lines are too long

View File

@ -1,52 +0,0 @@
.drop_zone {
outline: 1px solid orange;
}
.loader {
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Remove default bullets */
ul, .tree {
list-style-type: none;
}
/* Style the caret/arrow */
.tree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
.objTree-label {
cursor: pointer;
}
/* Create the caret/arrow with a unicode, and style it */
.tree-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) */
.tree-caret-down::before {
transform: rotate(90deg);
}

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

@ -4,45 +4,14 @@
<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 type="text/javascript" src="main.bundle.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="css/jsoneditor.min.css">
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/fileDrop.css">
<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>
<body class="testApp">
<div class="container">
<h1>GDML demo</h1>
</div>
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-lg-3">
<div class="card">
<div class="card-body">
<a>Load file:</a>
<input type="file" id="file_load_button" accept=".gdml, application/xml, application/json"/>
</div>
</div>
<div class="card">
<div class="card-body" id="drop_zone">
Load data
<br/>
(drag file here)
</div>
</div>
<div class="overflow-auto" id="tree"></div>
</div>
<div class="col-lg-6">
<div id="canvas"></div>
</div>
<div class="col-lg-3">
<div class="row" id="config"></div>
<div class="row" id="editor"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script></body>
<div class="container-fluid" id = "app"> </div>
</body>
</html>

View File

@ -5,9 +5,9 @@ import hep.dataforge.vis.editor.VisualObjectEditorFragment
import hep.dataforge.vis.editor.VisualObjectTreeFragment
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.Visual3D
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.fx.FX3DPlugin
import hep.dataforge.vis.spatial.fx.FXCanvas3D
import hep.dataforge.vis.spatial.gdml.toVisual
import javafx.geometry.Orientation
import javafx.scene.Parent
import javafx.stage.FileChooser
@ -36,10 +36,15 @@ class GDMLView : View() {
buttonbar {
button("Load GDML/json") {
action {
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
?: return@action
val visual: VisualGroup3D = Visual3D.readFile(file)
canvas.render(visual)
runAsync {
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
?: return@runAsync null
Visual3D.readFile(file)
} ui {
if (it != null) {
canvas.render(it)
}
}
}
}
}
@ -51,6 +56,14 @@ class GDMLView : View() {
}
}
init {
runAsync {
cubes().toVisual()
} ui {
canvas.render(it)
}
}
companion object {
private val fileNameFilter = arrayOf(
FileChooser.ExtensionFilter("GDML", "*.gdml", "*.xml"),

View File

@ -1,3 +1,5 @@
import kotlinx.atomicfu.plugin.gradle.withKotlinTargets
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import scientifik.jsDistDirectory
plugins {
@ -13,7 +15,7 @@ kotlin {
val installJS = tasks.getByName("jsBrowserDistribution")
js{
js {
browser {
dceTask {
dceOptions {
@ -28,14 +30,15 @@ kotlin {
jvm {
withJava()
compilations.findByName("main").apply {
tasks.getByName<ProcessResources>("jvmProcessResources") {
compilations[MAIN_COMPILATION_NAME]?.apply {
tasks.getByName<ProcessResources>(processResourcesTaskName) {
dependsOn(installJS)
afterEvaluate {
from(project.jsDistDirectory)
}
}
}
}
sourceSets {
@ -53,6 +56,7 @@ kotlin {
}
jsMain {
dependencies {
implementation(project(":ui:bootstrap"))
implementation("io.ktor:ktor-client-js:$ktorVersion")
implementation("io.ktor:ktor-client-serialization-js:$ktorVersion")
implementation(npm("text-encoding"))
@ -71,16 +75,3 @@ kotlin {
application {
mainClassName = "ru.mipt.npm.muon.monitor.server.MMServerKt"
}
//configure<JavaFXOptions> {
// 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

@ -62,6 +62,7 @@ class Model {
fun reset() {
map.values.forEach {
it.config
it.setProperty(Material3D.MATERIAL_COLOR_KEY, null)
}
tracks.removeAll()

View File

@ -1,15 +1,15 @@
package ru.mipt.npm.muon.monitor
import hep.dataforge.context.Context
import hep.dataforge.js.card
import hep.dataforge.js.component
import hep.dataforge.js.state
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.editor.configEditor
import hep.dataforge.vis.editor.objectTree
import hep.dataforge.vis.bootstrap.card
import hep.dataforge.vis.bootstrap.configEditor
import hep.dataforge.vis.bootstrap.objectTree
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import hep.dataforge.vis.spatial.specifications.Camera
import hep.dataforge.vis.spatial.specifications.Canvas
import hep.dataforge.vis.spatial.three.ThreeCanvas
@ -48,13 +48,14 @@ val MMApp = component<MMAppProps> { props ->
}
val visual = props.model.root
div("row") {
h1("mx-auto") {
+"Muon monitor demo"
+"GDML/JSON render demo"
}
}
div("row") {
div("col-lg-3 mh-100 px-0") {
div("col-lg-3 px-0 overflow-auto") {
//tree
card("Object tree") {
objectTree(visual, selected, select)
@ -154,7 +155,7 @@ val MMApp = component<MMAppProps> { props ->
else -> visual[selected]
}
if (selectedObject != null) {
configEditor(selectedObject, default = selectedObject.allProperties())
configEditor(selectedObject, default = selectedObject.allProperties(), key = selected)
}
}
}

View File

@ -9,6 +9,7 @@ import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.serialization.json.Json
import react.child
import react.dom.div
import react.dom.render
import kotlin.browser.document
@ -30,11 +31,13 @@ private class MMDemoApp : Application {
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
render(element) {
child(MMApp) {
attrs {
model = this@MMDemoApp.model
connection = this@MMDemoApp.connection
this.context = context
div("container-fluid h-100") {
child(MMApp) {
attrs {
model = this@MMDemoApp.model
connection = this@MMDemoApp.connection
this.context = context
}
}
}
}

View File

@ -1,6 +1,4 @@
import scientifik.DependencyConfiguration
import scientifik.FXModule
import scientifik.fx
import scientifik.*
plugins {
id("scientifik.mpp")
@ -8,7 +6,7 @@ plugins {
}
val fxVersion: String by rootProject.extra
fx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
useFx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
kotlin {

View File

@ -21,6 +21,7 @@ kotlin {
dependencies {
api(project(":dataforge-vis-spatial"))
api(project(":dataforge-vis-spatial-gdml"))
api(project(":ui:bootstrap"))
}
}
}

View File

@ -2,8 +2,8 @@ import hep.dataforge.context.Global
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.names.Name
import hep.dataforge.vis.editor.objectTree
import hep.dataforge.vis.editor.visualPropertyEditor
import hep.dataforge.vis.bootstrap.objectTree
import hep.dataforge.vis.bootstrap.visualPropertyEditor
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.box
@ -45,8 +45,6 @@ private class PlayGroundApp : Application {
}
}
}
}
}

View File

@ -25,6 +25,11 @@ pluginManagement {
rootProject.name = "dataforge-vis"
include(
":ui",
":ui:react",
":ui:ring",
":ui:material",
":ui:bootstrap",
":dataforge-vis-common",
":dataforge-vis-spatial",
":dataforge-vis-spatial-gdml",
@ -33,13 +38,3 @@ include(
":demo:muon-monitor",
":playground"
)
//if(file("../dataforge-core").exists()) {
// includeBuild("../dataforge-core"){
// dependencySubstitution {
// //substitute(module("hep.dataforge:dataforge-output")).with(project(":dataforge-output"))
// substitute(module("hep.dataforge:dataforge-output-jvm")).with(project(":dataforge-output"))
// substitute(module("hep.dataforge:dataforge-output-js")).with(project(":dataforge-output"))
// }
// }
//}

View File

@ -0,0 +1,16 @@
plugins {
id("scientifik.js")
}
val dataforgeVersion: String by rootProject.extra
kotlin {
target {
useCommonJs()
}
}
dependencies{
api(project(":dataforge-vis-common"))
api(project(":ui:react"))
}

View File

@ -1,4 +1,4 @@
package hep.dataforge.vis.editor
package hep.dataforge.vis.bootstrap
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem

View File

@ -1,15 +1,13 @@
package hep.dataforge.vis.editor
package hep.dataforge.vis.bootstrap
import hep.dataforge.js.RFBuilder
import hep.dataforge.js.card
import hep.dataforge.js.component
import hep.dataforge.js.state
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.isEmpty
import hep.dataforge.vis.react.RFBuilder
import hep.dataforge.vis.react.component
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
@ -29,7 +27,7 @@ interface TreeState : RState {
}
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by state{ props.selected?.startsWith(props.name) ?: false }
var expanded: Boolean by useState{ props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = {
expanded = !expanded

View File

@ -1,9 +1,13 @@
package hep.dataforge.js
package hep.dataforge.vis.bootstrap
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import kotlinx.html.*
import kotlinx.html.js.div
import kotlinx.html.js.onClickFunction
import org.w3c.dom.HTMLElement
import react.RBuilder
import react.ReactElement
import react.dom.*
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
@ -16,7 +20,7 @@ inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagCo
}
inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit) {
div("card w-100 h-100") {
div("card w-100") {
div("card-body") {
h3(classes = "card-title") {
+title
@ -26,7 +30,6 @@ inline fun RBuilder.card(title: String, crossinline block: RBuilder.() -> Unit)
}
}
fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
div("container-fluid") {
div("accordion") {
@ -59,7 +62,6 @@ fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, D
}
}
typealias AccordionBuilder = MutableList<Pair<String, DIV.() -> Unit>>
fun AccordionBuilder.entry(title: String, builder: DIV.() -> Unit) {
@ -71,8 +73,8 @@ fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.()
accordion(id, list)
}
fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>) {
div("container-fluid") {
fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>): ReactElement {
return div("container-fluid") {
div("accordion") {
attrs {
this.id = id
@ -111,13 +113,102 @@ fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.
}
}
fun RBuilder.namecrumbs(name: Name?, rootTitle: String, link: (Name) -> Unit) {
div("container-fluid p-0") {
nav {
attrs {
attributes["aria-label"] = "breadcrumb"
}
ol("breadcrumb") {
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+rootTitle
attrs {
onClickFunction = {
link(Name.EMPTY)
}
}
}
}
if (name != null) {
val tokens = ArrayList<NameToken>(name.length)
name.tokens.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+token.toString()
attrs {
onClickFunction = {
console.log("Selected = $fullName")
link(fullName)
}
}
}
}
}
}
}
}
}
}
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) {
fun RBuilder.accordion(id: String, builder: RAccordionBuilder.() -> Unit): ReactElement {
val list = ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
accordion(id, list)
return accordion(id, list)
}
fun joinStyles(vararg styles: String?) = styles.joinToString(separator = " ") { it ?: "" }
inline fun RBuilder.flexColumn(classes: String? = null, block: RDOMBuilder<DIV>.() -> Unit) =
div(joinStyles(classes, "d-flex flex-column"), block)
inline fun RBuilder.flexRow(classes: String? = null, block: RDOMBuilder<DIV>.() -> Unit) =
div(joinStyles(classes, "d-flex flex-row"), block)
enum class ContainerSize(val suffix: String) {
DEFAULT(""),
SM("-sm"),
MD("-md"),
LG("-lg"),
XL("-xl"),
FLUID("-fluid")
}
inline fun RBuilder.container(
classes: String? = null,
size: ContainerSize = ContainerSize.FLUID,
block: RDOMBuilder<DIV>.() -> Unit
): ReactElement = div(joinStyles(classes, "container${size.suffix}"), block)
enum class GridMaxSize(val suffix: String) {
NONE(""),
SM("-sm"),
MD("-md"),
LG("-lg"),
XL("-xl")
}
inline fun RBuilder.gridColumn(
weight: Int? = null,
classes: String? = null,
maxSize: GridMaxSize = GridMaxSize.NONE,
block: RDOMBuilder<DIV>.() -> Unit
): ReactElement {
val weightSuffix = weight?.let { "-$it" } ?: ""
return div(joinStyles(classes, "col${maxSize.suffix}$weightSuffix"), block)
}
inline fun RBuilder.gridRow(
classes: String? = null,
block: RDOMBuilder<DIV>.() -> Unit
): ReactElement {
return div(joinStyles(classes, "row"), block)
}

View File

@ -1,13 +1,14 @@
package hep.dataforge.vis.editor
package hep.dataforge.vis.bootstrap
import hep.dataforge.js.RFBuilder
import hep.dataforge.js.component
import hep.dataforge.js.state
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import hep.dataforge.values.Value
import hep.dataforge.vis.react.RFBuilder
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
@ -15,7 +16,7 @@ import org.w3c.dom.events.Event
import react.*
import react.dom.*
interface ConfigEditorProps : RProps {
interface ConfigEditorItemProps : RProps {
/**
* Root config object - always non null
@ -38,19 +39,22 @@ interface ConfigEditorProps : RProps {
var descriptor: NodeDescriptor?
}
private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> = component { props ->
configEditorItem(props)
}
private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
var expanded: Boolean by state { true }
val item = props.root[props.name]
var item: MetaItem<Config>? by state { props.root[props.name] }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val defaultItem = props.default?.get(props.name)
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
var actualItem: MetaItem<Meta>? by state { item ?: defaultItem ?: descriptorItem?.defaultItem() }
val token = props.name.last()?.toString() ?: "Properties"
var kostyl by state { false }
fun update() {
kostyl = !kostyl
item = props.root[props.name]
actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
}
useEffectWithCleanup(listOf(props.root)) {
@ -66,6 +70,15 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
expanded = !expanded
}
val valueChanged: (Value?) -> Unit = {
if (it == null) {
props.root.remove(props.name)
} else {
props.root[props.name] = it
}
update()
}
val removeClick: (Event) -> Unit = {
props.root.remove(props.name)
update()
@ -103,7 +116,16 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
keys.forEach { token ->
li("tree-item align-middle") {
configEditor(props.root, props.name + token, props.descriptor, props.default)
child(ConfigEditorItem) {
attrs {
this.key = props.name.toString()
this.root = props.root
this.name = props.name + token
this.default = props.default
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
@ -111,20 +133,27 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
}
is MetaItem.ValueItem -> {
div {
div("d-flex flex-row align-items-center") {
div("flex-grow-1 p-1 mr-auto tree-label") {
+token
attrs {
if (item == null) {
classes += "tree-label-inactive"
div("row form-group") {
div("col d-inline-flex") {
span("tree-label align-self-center") {
+token
attrs {
if (item == null) {
classes += "tree-label-inactive"
}
}
}
}
div("d-inline-flex") {
valueChooser(props.root, props.name, actualItem.value, descriptorItem as? ValueDescriptor)
div("col-6 d-inline-flex") {
valueChooser(
props.name,
actualItem,
descriptorItem as? ValueDescriptor,
valueChanged
)
}
div("d-inline-flex p-1") {
button(classes = "btn btn-link") {
div("col-auto d-inline-flex p-1") {
button(classes = "btn btn-link align-self-center") {
+"\u00D7"
attrs {
if (item == null) {
@ -141,30 +170,55 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorProps) {
}
}
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = component { configEditorItem(it) }
interface ConfigEditorProps : RProps {
var id: Name
var root: Config
var default: Meta?
var descriptor: NodeDescriptor?
}
val ConfigEditor = component<ConfigEditorProps> { props ->
child(ConfigEditorItem) {
attrs {
this.key = ""
this.root = props.root
this.name = Name.EMPTY
this.default = props.default
this.descriptor = props.descriptor
}
}
}
fun RBuilder.configEditor(
config: Config,
name: Name = Name.EMPTY,
descriptor: NodeDescriptor? = null,
default: Meta? = null
) {
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
render(this) {
child(ConfigEditor) {
attrs {
this.key = key?.toString() ?: ""
this.root = config
this.descriptor = descriptor
this.default = default
}
}
}
}
fun RBuilder.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null) {
child(ConfigEditor) {
attrs {
this.key = key?.toString() ?: ""
this.root = config
this.name = name
this.descriptor = descriptor
this.default = default
}
}
}
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
render(this) {
configEditor(config, Name.EMPTY, descriptor, default)
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null, key: Any? = null) {
child(ConfigEditor) {
attrs {
this.key = key?.toString() ?: ""
this.root = obj.config
this.descriptor = descriptor
this.default = default
}
}
}
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
configEditor(obj.config, Name.EMPTY, descriptor ?: obj.descriptor, default)
}

View File

@ -1,6 +1,5 @@
package hep.dataforge.vis.editor
package hep.dataforge.vis.bootstrap
import hep.dataforge.js.card
import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name

View File

@ -0,0 +1,182 @@
package hep.dataforge.vis.bootstrap
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.meta.value
import hep.dataforge.names.Name
import hep.dataforge.values.*
import hep.dataforge.vis.Colors
import hep.dataforge.vis.widgetType
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onKeyDownFunction
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.KeyboardEvent
import react.*
import react.dom.div
import react.dom.input
import react.dom.option
import react.dom.select
interface ValueChooserProps : RProps {
var item: MetaItem<*>?
var descriptor: ValueDescriptor?
var valueChanged: ((Value?) -> Unit)?
}
interface ValueChooserState : RState {
var value: Value?
var rawInput: Boolean?
}
class ValueChooserComponent(props: ValueChooserProps) : RComponent<ValueChooserProps, ValueChooserState>(props) {
private val element = createRef<HTMLElement>()
override fun ValueChooserState.init(props: ValueChooserProps) {
value = props.item.value
}
private fun getValue(): Value? {
val element = element.current ?: return null//state.element ?: return null
return when (element) {
is HTMLInputElement -> if (element.type == "checkbox") {
if (element.checked) True else False
} else {
element.value.asValue()
}
is HTMLSelectElement -> element.value.asValue()
else -> error("Unknown event target: $element")
}
}
private val valueChanged: (Event) -> Unit = { _ ->
setState {
value = getValue()
}
}
private val valueChangeAndCommit: (Event) -> Unit = { event ->
val res = getValue()
setState {
value = res
}
props.valueChanged?.invoke(res)
}
private val keyDown: (Event) -> Unit = { event ->
if (event is KeyboardEvent && event.key == "Enter") {
props.valueChanged?.invoke(getValue())
}
}
override fun shouldComponentUpdate(
nextProps: ValueChooserProps,
nextState: ValueChooserState
): Boolean = nextProps.item !== props.item
override fun componentDidUpdate(prevProps: ValueChooserProps, prevState: ValueChooserState, snapshot: Any) {
(element.current as? HTMLInputElement)?.let { element ->
if (element.type == "checkbox") {
element.checked = state.value?.boolean ?: false
} else {
element.value = state.value?.string ?: ""
}
element.indeterminate = state.value == null
}
// (state.element as? HTMLSelectElement)?.let { element ->
// state.value?.let { element.value = it.string }
// }
}
private fun RBuilder.stringInput() = input(type = InputType.text) {
attrs {
this.value = state.value?.string ?: ""
onChangeFunction = valueChanged
onKeyDownFunction = keyDown
}
ref = element
}
override fun RBuilder.render() {
div("align-self-center") {
val descriptor = props.descriptor
val type = descriptor?.type?.firstOrNull()
when {
state.rawInput == true -> stringInput()
descriptor?.widgetType == "color" -> input(type = InputType.color) {
ref = element
attrs {
this.value = state.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
onChangeFunction = valueChangeAndCommit
}
}
type == ValueType.BOOLEAN -> {
input(type = InputType.checkBox) {
ref = element
attrs {
checked = state.value?.boolean ?: false
onChangeFunction = valueChangeAndCommit
}
}
}
type == ValueType.NUMBER -> input(type = InputType.number) {
ref = element
attrs {
descriptor.attributes["step"].string?.let {
step = it
}
descriptor.attributes["min"].string?.let {
min = it
}
descriptor.attributes["max"].string?.let {
max = it
}
this.value = state.value?.string ?: ""
onChangeFunction = valueChanged
onKeyDownFunction = keyDown
}
}
descriptor?.allowedValues?.isNotEmpty() ?: false -> select {
descriptor!!.allowedValues.forEach {
option {
+it.string
}
}
ref = element
attrs {
this.value = state.value?.string ?: ""
multiple = false
onChangeFunction = valueChangeAndCommit
}
}
else -> stringInput()
}
}
}
}
internal fun RBuilder.valueChooser(
name: Name,
item: MetaItem<*>?,
descriptor: ValueDescriptor? = null,
callback: (Value?) -> Unit
) {
child(ValueChooserComponent::class) {
attrs {
key = name.toString()
this.item = item
this.descriptor = descriptor
this.valueChanged = callback
}
}
}

0
ui/build.gradle.kts Normal file
View File

View File

@ -0,0 +1,22 @@
plugins {
id("scientifik.js")
}
val dataforgeVersion: String by rootProject.extra
kotlin {
target {
useCommonJs()
}
}
dependencies{
api(project(":dataforge-vis-common"))
api(project(":ui:react"))
api("subroh0508.net.kotlinmaterialui:core:0.3.16")
api("subroh0508.net.kotlinmaterialui:lab:0.3.16")
api(npm("@material-ui/core","4.9.13"))
api(npm("@material-ui/lab","4.0.0-alpha.52"))
//api(npm("@material-ui/icons","4.9.1"))
}

View File

@ -0,0 +1,193 @@
package hep.dataforge.vis.material
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty
import hep.dataforge.names.plus
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import kotlinx.css.Display
import kotlinx.css.display
import kotlinx.css.flexGrow
import kotlinx.css.flexShrink
import kotlinx.html.js.onClickFunction
import materialui.components.button.button
import materialui.components.grid.enums.GridAlignItems
import materialui.components.grid.enums.GridJustify
import materialui.components.grid.grid
import materialui.components.typography.typographyH6
import materialui.lab.components.treeItem.treeItem
import materialui.lab.components.treeView.treeView
import org.w3c.dom.Element
import org.w3c.dom.events.Event
import react.*
import react.dom.render
import react.dom.span
import styled.css
import styled.styledDiv
interface ConfigEditorProps : RProps {
/**
* Root config object - always non null
*/
var root: Config
/**
* Full path to the displayed node in [root]. Could be empty
*/
var name: Name
/**
* Root default
*/
var default: Meta?
/**
* Root descriptor
*/
var descriptor: NodeDescriptor?
}
private fun RBuilder.configEditorItem(
root: Config,
name: Name,
descriptor: NodeDescriptor?,
default: Meta?
) {
val item = root[name]
val descriptorItem: ItemDescriptor? = descriptor?.get(name)
val defaultItem = default?.get(name)
val actualItem: MetaItem<Meta>? = item ?: defaultItem ?: descriptorItem?.defaultItem()
val token = name.last()?.toString() ?: "Properties"
val removeClick: (Event) -> Unit = {
root.remove(name)
}
treeItem {
attrs {
nodeId = name.toString()
label {
row {
attrs {
alignItems = GridAlignItems.stretch
justify = GridJustify.spaceBetween
spacing(1)
}
grid {
typographyH6 {
+token
}
}
if (actualItem is MetaItem.ValueItem) {
styledDiv {
css {
display = Display.flex
flexGrow = 1.0
}
valueChooser(root, name, actualItem.value, descriptorItem as? ValueDescriptor)
}
}
if (!name.isEmpty()) {
styledDiv {
css {
display = Display.flex
flexShrink = 1.0
}
button {
+"\u00D7"
attrs {
if (item == null) {
disabled = true
} else {
onClickFunction = removeClick
}
}
}
}
}
}
}
}
if (actualItem is MetaItem.NodeItem) {
val keys = buildSet<NameToken> {
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
add(NameToken(it))
}
item?.node?.items?.keys?.let { addAll(it) }
defaultItem?.node?.items?.keys?.let { addAll(it) }
}
keys.forEach { token ->
configEditorItem(root, name + token, descriptor, default)
}
}
}
}
val ConfigEditor: FunctionalComponent<ConfigEditorProps> = component { props ->
var kostyl by state { false }
fun update() {
kostyl = !kostyl
}
useEffectWithCleanup(listOf(props.root)) {
props.root.onChange(this) { name, _, _ ->
if (name == props.name) {
update()
}
}
return@useEffectWithCleanup { props.root.removeListener(this) }
}
treeView {
attrs {
defaultCollapseIcon {
span {
+"-"
}
//child(ExpandMoreIcon::class) {}
}//{<ExpandMoreIcon />}
defaultExpandIcon {
span {
+"+"
}
//child(ChevronRightIcon::class) {}
}//{<ChevronRightIcon />}
set("disableSelection", true)
}
configEditorItem(props.root, props.name, props.descriptor, props.default)
}
}
fun RBuilder.configEditor(
config: Config,
name: Name = Name.EMPTY,
descriptor: NodeDescriptor? = null,
default: Meta? = null
) {
child(ConfigEditor) {
attrs {
this.root = config
this.name = name
this.descriptor = descriptor
this.default = default
}
}
}
fun Element.configEditor(config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null) {
render(this) {
configEditor(config, Name.EMPTY, descriptor, default)
}
}
fun RBuilder.configEditor(obj: Configurable, descriptor: NodeDescriptor? = obj.descriptor, default: Meta? = null) {
configEditor(obj.config, Name.EMPTY, descriptor ?: obj.descriptor, default)
}

View File

@ -0,0 +1,16 @@
package hep.dataforge.vis.material
import react.Component
import react.RClass
import react.RProps
import react.RState
//@JsModule("@material-ui/icons/ExpandMore")
//external class ExpandMoreIcon : Component<RProps, RState>{
// override fun render(): dynamic
//}
//
//@JsModule("@material-ui/icons/ChevronRight")
//external class ChevronRightIcon : Component<RProps, RState>{
// override fun render(): dynamic
//}

View File

@ -0,0 +1,98 @@
package hep.dataforge.vis.material
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import kotlinx.html.DIV
import materialui.components.card.card
import materialui.components.cardcontent.cardContent
import materialui.components.cardheader.cardHeader
import materialui.components.container.container
import materialui.components.container.enums.ContainerMaxWidth
import materialui.components.expansionpanel.expansionPanel
import materialui.components.expansionpaneldetails.expansionPanelDetails
import materialui.components.expansionpanelsummary.expansionPanelSummary
import materialui.components.grid.GridElementBuilder
import materialui.components.grid.enums.GridDirection
import materialui.components.grid.enums.GridStyle
import materialui.components.grid.grid
import materialui.components.paper.paper
import materialui.components.typography.typographyH3
import materialui.components.typography.typographyH5
import react.RBuilder
import react.RProps
import react.child
import react.dom.RDOMBuilder
fun accordionComponent(elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>) =
component<RProps> {
val expandedIndex: Int? by state { null }
container {
attrs {
maxWidth = ContainerMaxWidth.`false`
}
elements.forEachIndexed { index, (header, body) ->
expansionPanel {
attrs {
expanded = index == expandedIndex
}
expansionPanelSummary {
typographyH5 {
+header
}
}
expansionPanelDetails {
this.body()
}
}
}
}
}
typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
add(title to builder)
}
fun RBuilder.accordion(builder: RAccordionBuilder.() -> Unit) {
val list: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>> =
ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
child(accordionComponent(list))
}
fun RBuilder.materialCard(title: String, block: RBuilder.() -> Unit) {
card {
cardHeader {
attrs {
this.title = typographyH5 {
+title
}
}
}
cardContent {
paper {
this.block()
}
}
}
}
fun RBuilder.column(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
grid(*classMap) {
attrs {
container = true
direction = GridDirection.column
}
block()
}
fun RBuilder.row(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
grid(*classMap) {
attrs {
container = true
direction = GridDirection.row
}
block()
}

View File

@ -0,0 +1,92 @@
package hep.dataforge.vis.material
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import hep.dataforge.vis.VisualGroup
import hep.dataforge.vis.VisualObject
import hep.dataforge.vis.isEmpty
import hep.dataforge.vis.react.component
import hep.dataforge.vis.react.state
import materialui.lab.components.treeItem.treeItem
import materialui.lab.components.treeView.treeView
import react.FunctionalComponent
import react.RBuilder
import react.RProps
import react.child
import react.dom.span
interface ObjectTreeProps : RProps {
var name: Name
var selected: Name?
var obj: VisualObject
var clickCallback: (Name?) -> Unit
}
private fun RBuilder.treeBranch(name: Name, obj: VisualObject): Unit {
treeItem {
val token = name.last()?.toString() ?: "World"
attrs {
nodeId = name.toString()
label {
span {
+token
}
}
}
if (obj is VisualGroup) {
obj.children.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
.forEach { (childToken, child) ->
treeBranch(name + childToken, child)
}
}
}
}
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
var selected: String? by state { props.selected.toString() }
treeView {
attrs {
this.selected = selected
this.onNodeSelect = { _, selectedItem ->
@Suppress("CAST_NEVER_SUCCEEDS")
selected = (selectedItem as? String)
val itemName = selected?.toName()
props.clickCallback(itemName)
Unit
}
defaultCollapseIcon {
span{
+"-"
}
//child(ExpandMoreIcon::class) {}
}//{<ExpandMoreIcon />}
defaultExpandIcon {
span{
+"+"
}
//child(ChevronRightIcon::class) {}
}//{<ChevronRightIcon />}
}
treeBranch(props.name, props.obj)
}
}
fun RBuilder.objectTree(
visualObject: VisualObject,
selected: Name? = null,
clickCallback: (Name?) -> Unit = {}
) {
child(ObjectTree) {
attrs {
this.name = Name.EMPTY
this.obj = visualObject
this.selected = selected
this.clickCallback = clickCallback
}
}
}

View File

@ -0,0 +1,114 @@
package hep.dataforge.vis.material
import hep.dataforge.meta.Config
import hep.dataforge.meta.descriptors.ValueDescriptor
import hep.dataforge.meta.get
import hep.dataforge.meta.number
import hep.dataforge.meta.setValue
import hep.dataforge.names.Name
import hep.dataforge.values.*
import hep.dataforge.vis.widgetType
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onKeyDownFunction
import materialui.components.input.input
import materialui.components.select.select
import materialui.components.slider.slider
import materialui.components.switches.switch
import materialui.components.textfield.textField
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.KeyboardEvent
import react.RBuilder
import react.dom.option
internal fun RBuilder.valueChooser(root: Config, name: Name, value: Value, descriptor: ValueDescriptor?) {
val onValueChange: (Event) -> Unit = { event ->
if (event !is KeyboardEvent || event.key == "Enter") {
val res = when (val t = event.target) {
// (it.target as HTMLInputElement).value
is HTMLInputElement -> if (t.type == "checkbox") {
if (t.checked) True else False
} else {
t.value.asValue()
}
is HTMLSelectElement -> t.value.asValue()
else -> error("Unknown event target: $t")
}
try {
root.setValue(name, res)
} catch (ex: Exception) {
console.error("Can't set config property ${name} to $res")
}
}
}
val type = descriptor?.type?.firstOrNull()
when {
descriptor?.widgetType == "slider" -> slider {
attrs {
descriptor.attributes["step"].number?.let {
step = it
}
descriptor.attributes["min"].number?.let {
min = it
}
descriptor.attributes["max"].number?.let {
max = it
}
this.defaultValue = value.number
onChangeFunction = onValueChange
}
}
descriptor?.widgetType == "color" -> input {
attrs {
fullWidth = true
this.type = InputType.color
this.value = value.string
onChangeFunction = onValueChange
}
}
type == ValueType.BOOLEAN -> switch {
attrs {
defaultChecked = value.boolean
onChangeFunction = onValueChange
}
}
type == ValueType.NUMBER -> textField {
attrs {
fullWidth = true
this.type = InputType.number
defaultValue = value.string
onChangeFunction = onValueChange
//onKeyDownFunction = onValueChange
}
}
descriptor?.allowedValues?.isNotEmpty() ?: false -> select {
descriptor!!.allowedValues.forEach {
option {
+it.string
}
}
attrs {
fullWidth = true
multiple = false
onChangeFunction = onValueChange
}
}
else -> textField {
attrs {
this.type = InputType.text
fullWidth = true
this.defaultValue = value.string
//onFocusOutFunction = onValueChange
onKeyDownFunction = onValueChange
}
}
}
}

View File

@ -0,0 +1,37 @@
package materialui.components.slider
import kotlinx.html.DIV
import kotlinx.html.Tag
import materialui.components.MaterialElementBuilder
import materialui.components.getValue
import materialui.components.inputbase.enums.InputBaseStyle
import materialui.components.setValue
import react.RClass
import react.ReactElement
class SliderElementBuilder<Props: SliderProps> internal constructor(
type: RClass<Props>,
classMap: List<Pair<Enum<*>, String>>
) : MaterialElementBuilder<DIV, Props>(type, classMap, { DIV(mapOf(), it) }) {
fun Tag.classes(vararg classMap: Pair<InputBaseStyle, String>) {
classes(classMap.toList())
}
var Tag.defaultValue: Number? by materialProps
var Tag.disabled: Boolean? by materialProps// = false
var Tag.getAriaLabel: String? by materialProps
var Tag.getAriaValueText: String? by materialProps
var Tag.marks: Array<String>? by materialProps
var Tag.max: Number? by materialProps// = 100
var Tag.min: Number? by materialProps// = 0,
var Tag.name: String? by materialProps
var Tag.onChange: ((dynamic, Number) -> Unit)? by materialProps
var Tag.onChangeCommitted: ((dynamic, Number) -> Unit)? by materialProps
var Tag.orientation: SliderOrientation? by materialProps
var Tag.scale: ((Number) -> Number)? by materialProps// {it}
var Tag.step: Number? by materialProps// = 1,
//ThumbComponent = 'span',
var Tag.track: SliderTrack by materialProps
var Tag.value: Number? by materialProps
var Tag.ValueLabelComponent: ReactElement? by materialProps
var Tag.valueLabelDisplay: SliderValueLabelDisplay by materialProps
}

View File

@ -0,0 +1,18 @@
package materialui.components.slider
enum class SliderOrientation {
horizontal,
vertical
}
enum class SliderTrack{
normal,
`false`,
inverted,
}
enum class SliderValueLabelDisplay{
on,
auto,
off
}

View File

@ -0,0 +1,38 @@
package materialui.components.slider
import materialui.components.StandardProps
import materialui.components.input.enums.InputStyle
import react.RBuilder
import react.RClass
import react.ReactElement
@JsModule("@material-ui/core/Slider")
private external val sliderModule: dynamic
external interface SliderProps : StandardProps {
var defaultValue: Number?
var disabled: Boolean?// = false
var getAriaLabel: String?
var getAriaValueText: String?
var marks: Array<String>?
var max: Number?// = 100
var min: Number?// = 0,
var name: String?
var onChange: ((dynamic, Number) -> Unit)?
var onChangeCommitted: ((dynamic, Number) -> Unit)?
var orientation: SliderOrientation?
var scale: ((Number) -> Number)?// {it}
var step: Number? // = 1,
//ThumbComponent = 'span',
var track: SliderTrack
var value: Number?
var ValueLabelComponent: ReactElement?
var valueLabelDisplay: SliderValueLabelDisplay
//valueLabelFormat = Identity,
}
@Suppress("UnsafeCastFromDynamic")
private val sliderComponent: RClass<SliderProps> = sliderModule.default
fun RBuilder.slider(vararg classMap: Pair<InputStyle, String>, block: SliderElementBuilder<SliderProps>.() -> Unit) =
child(SliderElementBuilder(sliderComponent, classMap.toList()).apply(block).create())

30
ui/react/build.gradle.kts Normal file
View File

@ -0,0 +1,30 @@
plugins {
id("scientifik.js")
}
kotlin {
target {
useCommonJs()
}
}
dependencies{
api("org.jetbrains:kotlin-react:16.13.1-pre.104-kotlin-1.3.72")
api("org.jetbrains:kotlin-react-dom:16.13.1-pre.104-kotlin-1.3.72")
api("org.jetbrains.kotlinx:kotlinx-html:0.6.12")
api("org.jetbrains:kotlin-extensions:1.0.1-pre.104-kotlin-1.3.72")
api("org.jetbrains:kotlin-css-js:1.0.0-pre.94-kotlin-1.3.70")
api("org.jetbrains:kotlin-styled:1.0.0-pre.104-kotlin-1.3.72")
api(npm("core-js", "2.6.5"))
api(npm("react", "16.13.1"))
api(npm("react-dom", "16.13.1"))
api(npm("react-is", "16.13.0"))
api(npm("inline-style-prefixer", "5.1.0"))
api(npm("styled-components", "4.3.2"))
}

View File

@ -1,4 +1,4 @@
package hep.dataforge.js
package hep.dataforge.vis.react
import react.*
import kotlin.properties.ReadWriteProperty

22
ui/ring/build.gradle.kts Normal file
View File

@ -0,0 +1,22 @@
plugins {
id("scientifik.js")
}
val dataforgeVersion: String by rootProject.extra
kotlin {
target {
useCommonJs()
}
}
dependencies{
api(project(":ui:react"))
implementation(npm("@jetbrains/logos", "1.1.6"))
implementation(npm("@jetbrains/ring-ui", "3.0.13"))
implementation(npm("svg-inline-loader", "0.8.0"))
}

View File

@ -0,0 +1,34 @@
package ringui
import react.RBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/alert/alert.js
external interface AlertProps : WithClassName {
var timeout: Number
var onCloseRequest: () -> Unit
var onClose: () -> Unit
var isShaking: Boolean
var isClosing: Boolean
var inline: Boolean
var showWithAnimation: Boolean
var closeable: Boolean
var type: AlertType
}
typealias AlertType = String
object AlertTypes {
var ERROR = "error"
var MESSAGE = "message"
var SUCCESS = "success"
var WARNING = "warning"
var LOADING = "loading"
}
fun RBuilder.ringAlert(handler: RHandler<AlertProps>) {
RingUI.Alert {
handler()
}
}

View File

@ -0,0 +1,35 @@
package ringui
import org.w3c.dom.events.MouseEvent
import react.RBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/button/button.js
external interface ButtonProps : WithClassName {
var theme: String
var active: Boolean
var danger: Boolean
var delayed: Boolean
var loader: Boolean
var primary: Boolean
var short: Boolean
var text: Boolean
var inline: Boolean
var dropdown: Boolean
var href: String
var icon: dynamic /* string | func */
var iconSize: Number
var iconClassName: String
var onMouseDown: (MouseEvent) -> Unit
}
fun RBuilder.ringButton(handler: RHandler<ButtonProps>) {
RingUI.Button {
handler()
}
}

View File

@ -0,0 +1,28 @@
package ringui
import react.RBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/dialog/dialog.js
external interface DialogProps : WithClassName {
var contentClassName: String
var show: Boolean
var showCloseButton: Boolean
var onOverlayClick: () -> Unit
var onEscPress: () -> Unit
var onCloseClick: () -> Unit
// onCloseAttempt is a common callback for ESC pressing and overlay clicking.
// Use it if you don't need different behaviors for this cases.
var onCloseAttempt: () -> Unit
// focusTrap may break popups inside dialog, so use it carefully
var trapFocus: Boolean
var autoFocusFirst: Boolean
}
fun RBuilder.ringDialog(show: Boolean, handler: RHandler<DialogProps>) {
RingUI.Dialog {
attrs.show = show
handler()
}
}

View File

@ -0,0 +1,21 @@
package ringui
import react.RBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/icon/icon.js
external interface IconProps : WithClassName {
var color: String
var glyph: dynamic /* string | func */
var height: Number
var size: Number
var width: Number
var loading: Boolean
}
fun RBuilder.ringIcon(handler: RHandler<IconProps>) {
RingUI.Icon {
handler()
}
}

View File

@ -0,0 +1,24 @@
package ringui
import org.w3c.dom.events.MouseEvent
import react.RBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/link/link.js
external interface LinkProps : WithClassName {
var innerClassName: String
var active: Boolean
var inherit: Boolean
var pseudo: Boolean
var hover: Boolean
var href: String
var onPlainLeftClick: (MouseEvent) -> Unit
var onClick: (MouseEvent) -> Unit
}
fun RBuilder.ringLink(handler: RHandler<LinkProps>) {
RingUI.Link {
handler()
}
}

View File

@ -0,0 +1,14 @@
package ringui
import ringui.header.HeaderProps
import react.RClass
@JsModule("@jetbrains/ring-ui")
external object RingUI {
val Alert: RClass<AlertProps>
val Button: RClass<ButtonProps>
val Dialog: RClass<DialogProps>
val Header: RClass<HeaderProps>
val Link: RClass<LinkProps>
val Icon: RClass<IconProps>
}

View File

@ -0,0 +1,38 @@
package ringui
import react.RBuilder
import react.RClass
import react.RHandler
import react.RProps
@JsModule("@jetbrains/ring-ui/components/user-card/user-card")
private external object UserCardModule {
val UserCard: RClass<UserCardProps>
}
// https://github.com/JetBrains/ring-ui/blob/master/components/user-card/card.js
external interface UserCardProps : RProps {
var user: UserCardModel
var wording: UserCardWording
}
data class UserCardModel(
val name: String,
val login: String,
val avatarUrl: String,
val email: String? = null,
val href: String? = null
)
data class UserCardWording(
val banned: String,
val online: String,
val offline: String
)
fun RBuilder.ringUserCard(user: UserCardModel, handler: RHandler<UserCardProps> = {}) {
UserCardModule.UserCard {
attrs.user = user
handler()
}
}

View File

@ -0,0 +1,31 @@
package ringui.header
import ringui.RingUI
import react.RBuilder
import react.RClass
import react.RHandler
import react.dom.WithClassName
@JsModule("@jetbrains/ring-ui/components/header/header")
internal external object HeaderModule {
val RerenderableHeader: RClass<HeaderProps>
val Logo: RClass<HeaderLogoProps>
val Tray: RClass<HeaderTrayProps>
val TrayIcon: RClass<WithClassName>
val Profile: RClass<WithClassName>
val SmartProfile: RClass<WithClassName>
val Services: RClass<WithClassName>
val SmartServices: RClass<WithClassName>
}
// https://github.com/JetBrains/ring-ui/blob/master/components/header/header.js
external interface HeaderProps : WithClassName {
var spaced: Boolean
var theme: String
}
fun RBuilder.ringHeader(handler: RHandler<HeaderProps>) {
RingUI.Header {
handler()
}
}

View File

@ -0,0 +1,21 @@
package ringui.header
import ringui.IconProps
import kotlinx.html.A
import react.RElementBuilder
import react.RHandler
import styled.StyledDOMBuilder
external interface HeaderLogoProps : IconProps
fun StyledDOMBuilder<A>.ringLogo(handler: RHandler<HeaderLogoProps>) {
HeaderModule.Logo {
handler()
}
}
fun RElementBuilder<HeaderProps>.ringLogo(handler: RHandler<HeaderLogoProps>) {
HeaderModule.Logo {
handler()
}
}

View File

@ -0,0 +1,26 @@
package ringui.header
import ringui.ButtonProps
import react.RElementBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/header/tray.js
external interface HeaderTrayProps : WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/header/tray-icon.js
external interface HeaderTrayIconProps : ButtonProps {
var rotatable: Boolean
}
fun RElementBuilder<HeaderProps>.ringTray(handler: RHandler<HeaderTrayProps>) {
HeaderModule.Tray {
handler()
}
}
fun RElementBuilder<HeaderTrayProps>.ringTrayIcon(handler: RHandler<WithClassName>) {
HeaderModule.TrayIcon {
handler()
}
}

View File

@ -0,0 +1,20 @@
package ringui.island
import react.RElementBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/island/content.js
external interface IslandContentProps : WithClassName {
var scrollableWrapperClassName: String
var fade: Boolean
var bottomBorder: Boolean
var onScroll: () -> Unit
var onScrollToBottom: () -> Unit
}
fun RElementBuilder<IslandProps>.ringIslandContent(handler: RHandler<IslandContentProps>) {
IslandModule.Content {
handler()
}
}

View File

@ -0,0 +1,18 @@
package ringui.island
import react.RElementBuilder
import react.RHandler
import react.dom.WithClassName
// https://github.com/JetBrains/ring-ui/blob/master/components/island/header.js
external interface IslandHeaderProps : WithClassName {
var border: Boolean
var wrapWithTitle: Boolean
var phase: Number
}
fun RElementBuilder<IslandProps>.ringIslandHeader(handler: RHandler<IslandHeaderProps>) {
IslandModule.Header {
handler()
}
}

View File

@ -0,0 +1,32 @@
package ringui.island
import react.RBuilder
import react.RClass
import react.RHandler
import react.dom.WithClassName
@JsModule("@jetbrains/ring-ui/components/island/island")
internal external object IslandModule {
val default: RClass<IslandProps>
val Content: RClass<IslandContentProps>
val Header: RClass<IslandHeaderProps>
val AdaptiveIsland: RClass<IslandProps>
}
// https://github.com/JetBrains/ring-ui/blob/master/components/island/island.js
external interface IslandProps : WithClassName {
val narrow: Boolean
val withoutPaddings: Boolean
}
fun RBuilder.ringIsland(handler: RHandler<IslandProps>) {
IslandModule.default {
handler()
}
}
fun RBuilder.ringAdaptiveIsland(handler: RHandler<IslandProps>) {
IslandModule.AdaptiveIsland {
handler()
}
}

7
ui/ring/webpack.config.d/01.ring.js vendored Normal file
View File

@ -0,0 +1,7 @@
// wrap is useful, because declaring variables in module can be already declared
// module creates own lexical environment
;(function (config) {
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules);
})(config);

15
ui/ring/webpack.config.d/02.svg.js vendored Normal file
View File

@ -0,0 +1,15 @@
// wrap is useful, because declaring variables in module can be already declared
// module creates own lexical environment
;(function (config) {
const path = require("path");
config.module.rules.push(
{
test: /\.svg$/,
loader: "svg-inline-loader",
options: {removeSVGTagAttrs: false},
include: [
path.resolve(require.resolve("@jetbrains/logos"), "..", "..")
]
}
);
})(config);