diff --git a/README.md b/README.md index dbddf3f7..6e94454f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# DataForge Plugins for Visualisation +[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) + +# DataForge plugins for visualisation This repository contains [DataForge](http://npm.mipt.ru/dataforge/) (also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in diff --git a/build.gradle.kts b/build.gradle.kts index 48899168..0378807d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,10 @@ -val dataforgeVersion by extra("0.1.3") +import scientifik.useSerialization + +val dataforgeVersion by extra("0.1.5-dev-6") plugins { - val kotlinVersion = "1.3.50" - val toolsVersion = "0.2.0" + val kotlinVersion = "1.3.61" + val toolsVersion = "0.3.2" kotlin("jvm") version kotlinVersion apply false id("kotlin-dce-js") version kotlinVersion apply false @@ -15,14 +17,22 @@ plugins { allprojects { repositories { + mavenLocal() maven("https://dl.bintray.com/pdvrieze/maven") maven("http://maven.jzy3d.org/releases") + maven("https://kotlin.bintray.com/js-externals") +// maven("https://dl.bintray.com/gbaldeck/kotlin") +// maven("https://dl.bintray.com/rjaros/kotlin") } group = "hep.dataforge" version = "0.1.0-dev" } +subprojects{ + this.useSerialization() +} + val githubProject by extra("dataforge-vis") val bintrayRepo by extra("dataforge") diff --git a/dataforge-vis-common/build.gradle.kts b/dataforge-vis-common/build.gradle.kts index 678a9e72..548c1be5 100644 --- a/dataforge-vis-common/build.gradle.kts +++ b/dataforge-vis-common/build.gradle.kts @@ -1,27 +1,50 @@ +import org.openjfx.gradle.JavaFXOptions +import scientifik.useSerialization + plugins { id("scientifik.mpp") -} - -scientifik{ - withSerialization() + id("org.openjfx.javafxplugin") } val dataforgeVersion: String by rootProject.extra +//val kvisionVersion: String by rootProject.extra("2.0.0-M1") + +useSerialization() kotlin { + jvm{ + withJava() + } + sourceSets { - val commonMain by getting { + commonMain{ dependencies { api("hep.dataforge:dataforge-output:$dataforgeVersion") } } - val jsMain by getting { + jvmMain{ + dependencies { + api("no.tornado:tornadofx:1.7.19") + //api("no.tornado:tornadofx-controlsfx:0.1.1") + api("de.jensd:fontawesomefx-fontawesome:4.7.0-11"){ + exclude(group = "org.openjfx") + } + api("de.jensd:fontawesomefx-commons:11.0"){ + exclude(group = "org.openjfx") + } + } + } + jsMain{ dependencies { api("hep.dataforge:dataforge-output-html:$dataforgeVersion") - api(npm("text-encoding")) - api("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50") - api(npm("core-js")) + api(npm("bootstrap","4.4.1")) + implementation(npm("jsoneditor")) + implementation(npm("file-saver")) } } } -} \ No newline at end of file +} + +configure { + modules("javafx.controls") +} diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt index 38ddf19f..889cb0dc 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt @@ -1,7 +1,5 @@ package hep.dataforge.vis.common -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaItem import hep.dataforge.names.* import kotlinx.serialization.Transient @@ -17,46 +15,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup /** * A map of top level named children */ - abstract override val children: Map //get() = _children - - /** - * Styles, defined in this group. A style could be defined but not applied - * TODO replace by custom object with get/set functionality - */ - protected abstract val styleSheet: MutableMap - - override fun getStyle(name: Name): Meta? = styleSheet[name] - - override fun addStyle(name: Name, meta: Meta, apply: Boolean) { - fun VisualObject.applyStyle(name: Name, meta: Meta) { - if (styles.contains(name)) { - //full update - //TODO do a fine grained update - if (this is AbstractVisualObject) { - styleChanged() - } else { - propertyChanged(EmptyName) - } - } - if (this is VisualGroup) { - this.children.forEach { (_, child) -> - child.applyStyle(name, meta) - } - } - } - styleSheet[name] = meta - if (apply) { - applyStyle(name, meta) - } - } - - -// init { -// //Do after deserialization -// children.values.forEach { -// it.parent = this -// } -// } + abstract override val children: Map override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) { super.propertyChanged(name, before, after) @@ -114,7 +73,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup protected abstract fun createGroup(name: Name): MutableVisualGroup /** - * Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not + * Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not * allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed. */ override fun set(name: Name, child: VisualObject?) { @@ -141,22 +100,13 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup structureChangeListeners.forEach { it.callback(name, child) } } - operator fun set(key: String, child: VisualObject?) = if (key.isBlank()) { - child?.let { addStatic(child) } - } else { - set(key.asName(), child) - } - -// operator fun set(key: String?, child: VisualObject?) = set(key ?: "", child) - - protected fun MetaBuilder.updateChildren() { - //adding named children - children.forEach { - "children[${it.key}]" to it.value.toMeta() + operator fun set(key: String, child: VisualObject?): Unit { + if (key.isBlank()) { + if(child!= null) { + addStatic(child) + } + } else { + set(key.toName(), child) } } - - override fun MetaBuilder.updateMeta() { - updateChildren() - } } \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt index e63382ca..7701eb1c 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt @@ -1,9 +1,8 @@ package hep.dataforge.vis.common import hep.dataforge.meta.* -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name -import hep.dataforge.names.toName +import hep.dataforge.names.asName import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY import kotlinx.serialization.Transient @@ -17,15 +16,25 @@ abstract class AbstractVisualObject : VisualObject { @Transient override var parent: VisualObject? = null - abstract override var properties: Config? + protected abstract var properties: Config? - override var styles: List - get() = properties?.get(STYLE_KEY).stringList.map(String::toName) + override var styles: List + get() = properties?.get(STYLE_KEY).stringList set(value) { - setProperty(STYLE_KEY, value.map { it.toString() }) - styleChanged() + //val allStyles = (field + value).distinct() + setProperty(STYLE_KEY, value) + updateStyles(value) } + protected fun updateStyles(names: List) { + names.mapNotNull { findStyle(it) }.asSequence() + .flatMap { it.items.asSequence() } + .distinctBy { it.key } + .forEach { + propertyChanged(it.key.asName(), null, it.value) + } + } + /** * The config is initialized and assigned on-demand. * To avoid unnecessary allocations, one should access [properties] via [getProperty] instead. @@ -39,8 +48,10 @@ abstract class AbstractVisualObject : VisualObject { private val listeners = HashSet() override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) { - for (l in listeners) { - l.action(name, before, after) + if (before != after) { + for (l in listeners) { + l.action(name, before, after) + } } } @@ -52,10 +63,6 @@ abstract class AbstractVisualObject : VisualObject { listeners.removeAll { it.owner == owner } } - override fun setProperty(name: Name, value: Any?) { - config[name] = value - } - private var styleCache: Meta? = null /** @@ -66,14 +73,7 @@ abstract class AbstractVisualObject : VisualObject { styleCache = it } - - /** - * Helper to reset style cache - */ - protected fun styleChanged() { - styleCache = null - propertyChanged(EmptyName) - } + override fun allProperties(): Laminate = Laminate(properties, mergedStyles) override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { @@ -82,20 +82,12 @@ abstract class AbstractVisualObject : VisualObject { properties?.get(name) ?: mergedStyles[name] } } - - protected open fun MetaBuilder.updateMeta() {} - - override fun toMeta(): Meta = buildMeta { - "type" to this::class.simpleName - "properties" to properties - updateMeta() - } } -fun VisualObject.findStyle(styleName: Name): Meta? { - if (this is VisualGroup) { - val style = getStyle(styleName) - if (style != null) return style - } - return parent?.findStyle(styleName) -} \ No newline at end of file +//fun VisualObject.findStyle(styleName: Name): Meta? { +// if (this is VisualGroup) { +// val style = resolveStyle(styleName) +// if (style != null) return style +// } +// return parent?.findStyle(styleName) +//} \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt index 2e77fa45..3335c086 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt @@ -1,5 +1,8 @@ package hep.dataforge.vis.common +import hep.dataforge.meta.* +import hep.dataforge.values.ValueType +import hep.dataforge.values.int import kotlin.math.max /** @@ -178,6 +181,33 @@ object Colors { const val yellow = 0xFFFF00 const val yellowgreen = 0x9ACD32 + const val RED_KEY = "red" + const val GREEN_KEY = "green" + const val BLUE_KEY = "blue" + + /** + * Convert color represented as Meta to string of format #rrggbb + */ + fun fromMeta(item: MetaItem<*>): String { + return when (item) { + is MetaItem.NodeItem<*> -> { + val node = item.node + rgbToString( + node[RED_KEY].number?.toByte()?.toUByte() ?: 0u, + node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u, + node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u + ) + } + is MetaItem.ValueItem -> { + if (item.value.type == ValueType.NUMBER) { + rgbToString(item.value.int) + } else { + item.value.string + } + } + } + } + /** * Convert Int color to string of format #rrggbb */ @@ -190,8 +220,8 @@ object Colors { * Convert three bytes representing color to string of format #rrggbb */ fun rgbToString(red: UByte, green: UByte, blue: UByte): String { - fun colorToString(color: UByte): String{ - return color.toString(16).padStart(2,'0') + fun colorToString(color: UByte): String { + return color.toString(16).padStart(2, '0') } return buildString { append("#") @@ -200,4 +230,13 @@ object Colors { append(colorToString(blue)) } } + + /** + * Convert three bytes representing color to Meta + */ + fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = buildMeta { + RED_KEY put r.toInt() + GREEN_KEY put g.toInt() + BLUE_KEY put b.toInt() + } } \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/StyleSheet.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/StyleSheet.kt new file mode 100644 index 00000000..fce0b422 --- /dev/null +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/StyleSheet.kt @@ -0,0 +1,63 @@ +@file:UseSerializers(MetaSerializer::class) + +package hep.dataforge.vis.common + +import hep.dataforge.io.serialization.MetaSerializer +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import hep.dataforge.names.asName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers + +@Serializable +class StyleSheet() { + @Transient + internal var owner: VisualObject? = null + + constructor(owner: VisualObject) : this() { + this.owner = owner + } + + private val styleMap = HashMap() + + val items: Map get() = styleMap + + operator fun get(key: String): Meta? { + return styleMap[key] ?: (owner?.parent as? VisualGroup)?.styleSheet?.get(key) + } + + /** + * Define a style without notifying + */ + fun define(key: String, style: Meta?) { + if (style == null) { + styleMap.remove(key) + } else { + styleMap[key] = style + } + } + + operator fun set(key: String, style: Meta?) { + val oldStyle = styleMap[key] + define(key, style) + owner?.styleChanged(key, oldStyle, style) + } + + operator fun set(key: String, builder: MetaBuilder.() -> Unit) { + val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder) + set(key, newStyle.seal()) + } +} + +private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) { + if (styles.contains(key)) { + //TODO optimize set concatenation + val tokens: Collection = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet())) + .map { it.asName() } + tokens.forEach { parent?.propertyChanged(it, oldStyle?.get(it), newStyle?.get(it)) } + } + if (this is VisualGroup) { + this.forEach { it.styleChanged(key, oldStyle, newStyle) } + } +} \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/MutableVisualGroup.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt similarity index 72% rename from dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/MutableVisualGroup.kt rename to dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt index 761c0b20..467616c3 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/MutableVisualGroup.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt @@ -1,6 +1,5 @@ package hep.dataforge.vis.common -import hep.dataforge.meta.Meta import hep.dataforge.names.* import hep.dataforge.provider.Provider @@ -15,6 +14,8 @@ interface VisualGroup : Provider, Iterable, VisualObject { override val defaultTarget: String get() = VisualObject.TYPE + val styleSheet: StyleSheet? + override fun provideTop(target: String): Map = when (target) { VisualObject.TYPE -> children.flatMap { (key, value) -> @@ -25,7 +26,7 @@ interface VisualGroup : Provider, Iterable, VisualObject { } res.entries }.associate { it.toPair() } - //TODO add styles + STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap() else -> emptyMap() } @@ -35,17 +36,6 @@ interface VisualGroup : Provider, Iterable, VisualObject { */ override fun iterator(): Iterator = children.values.iterator() - /** - * Resolve style by its name - * TODO change to Config? - */ - fun getStyle(name: Name): Meta? - - /** - * Add or replace style with given name - */ - fun addStyle(name: Name, meta: Meta, apply: Boolean = true) - operator fun get(name: Name): VisualObject? { return when { name.isEmpty() -> this @@ -53,8 +43,27 @@ interface VisualGroup : Provider, Iterable, VisualObject { else -> (children[name.first()!!] as? VisualGroup)?.get(name.cutFirst()) } } + + /** + * A fix for serialization bug that writes all proper parents inside the tree after deserialization + */ + fun attachChildren() { + styleSheet?.owner = this + this.children.values.forEach { + it.parent = this + (it as? VisualGroup)?.attachChildren() + } + } + + companion object { + const val STYLE_TARGET = "style" + } } +data class StyleRef(val group: VisualGroup, val styleName: Name) + +val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty() + /** * Mutable version of [VisualGroup] */ @@ -75,4 +84,6 @@ interface MutableVisualGroup : VisualGroup { operator fun set(name: Name, child: VisualObject?) } -operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: EmptyName) \ No newline at end of file +operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY) + +fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null } \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt index 7bc3b262..0d94087c 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.Transient * A root type for display hierarchy */ @Type(TYPE) -interface VisualObject : MetaRepr, Configurable { +interface VisualObject : Configurable { /** * The parent object of this one. If null, this one is a root. @@ -24,14 +24,16 @@ interface VisualObject : MetaRepr, Configurable { var parent: VisualObject? /** - * Direct properties access + * All properties including styles and prototypes if present, but without inheritance */ - val properties: Config? + fun allProperties(): Laminate /** * Set property for this object */ - fun setProperty(name: Name, value: Any?) + fun setProperty(name: Name, value: Any?) { + config[name] = value + } /** * Get property including or excluding parent properties @@ -39,9 +41,11 @@ interface VisualObject : MetaRepr, Configurable { fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? /** - * Manually trigger property changed event. If [name] is empty, notify that the whole object is changed + * Trigger property invalidation event. If [name] is empty, notify that the whole object is changed */ - fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit + fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit + + fun propertyInvalidated(name: Name) = propertyChanged(name, null, null) /** * Add listener triggering on property change @@ -54,11 +58,9 @@ interface VisualObject : MetaRepr, Configurable { fun removeChangeListener(owner: Any?) /** - * List of names of styles applied to this object + * List of names of styles applied to this object. Order matters. Not inherited */ - var styles: List - - fun findAllStyles(): Laminate = Laminate(styles.distinct().mapNotNull(::findStyle)) + var styles: List companion object { const val TYPE = "visual" @@ -66,6 +68,7 @@ interface VisualObject : MetaRepr, Configurable { //const val META_KEY = "@meta" //const val TAGS_KEY = "@tags" + } } @@ -80,8 +83,33 @@ fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value) /** - * Apply style to [VisualObject] by adding it to the [style] list + * Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment. */ -fun VisualObject.applyStyle(name: String) { - styles = styles + name.toName() -} \ No newline at end of file +fun VisualObject.useStyle(name: String) { + styles = styles + name +} + +//private tailrec fun VisualObject.topGroup(): VisualGroup? { +// val parent = this.parent +// return if (parent == null) { +// this as? VisualGroup +// } +// else { +// parent.topGroup() +// } +//} +// +///** +// * Add or update given style on a top-most reachable parent group and apply it to this object +// */ +//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) { +// val styleName = name.toName() +// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this") +// useStyle(styleName) +//} + +tailrec fun VisualObject.findStyle(name: String): Meta? = + (this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name) + +fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle)) + diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualPlugin.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualPlugin.kt index 933e4736..39a1abc0 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualPlugin.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualPlugin.kt @@ -30,7 +30,8 @@ class VisualPlugin(meta: Meta) : AbstractPlugin(meta) { companion object : PluginFactory { override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP) override val type: KClass = VisualPlugin::class - override fun invoke(meta: Meta): VisualPlugin = VisualPlugin(meta) + + override fun invoke(meta: Meta, context: Context): VisualPlugin = VisualPlugin(meta) const val VISUAL_FACTORY_TYPE = "visual.factory" } diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/HMR.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/Application.kt similarity index 63% rename from dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/HMR.kt rename to dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/Application.kt index 5353fb03..53734c31 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/HMR.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/Application.kt @@ -1,14 +1,10 @@ -package hep.dataforge.vis +package hep.dataforge.js import kotlin.browser.document import kotlin.dom.hasClass external val module: Module -external interface Module { - val hot: Hot? -} - external interface Hot { val data: dynamic @@ -19,17 +15,31 @@ external interface Hot { fun dispose(callback: (data: dynamic) -> Unit) } -external fun require(name: String): dynamic - -abstract class ApplicationBase { - open val stateKeys: List get() = emptyList() - - abstract fun start(state: Map) - open fun dispose(): Map = emptyMap() +external interface Module { + val hot: Hot? } -fun startApplication(builder: () -> ApplicationBase) { - fun start(state: dynamic): ApplicationBase? { +/** + * Base interface for applications. + * + * Base interface for applications supporting Hot Module Replacement (HMR). + */ +interface Application { + /** + * Starting point for an application. + * @param state Initial state between Hot Module Replacement (HMR). + */ + fun start(state: Map) + + /** + * Ending point for an application. + * @return final state for Hot Module Replacement (HMR). + */ + fun dispose(): Map = emptyMap() +} + +fun startApplication(builder: () -> Application) { + fun start(state: dynamic): Application? { return if (document.body?.hasClass("testApp") == true) { val application = builder() @@ -42,7 +52,7 @@ fun startApplication(builder: () -> ApplicationBase) { } } - var application: ApplicationBase? = null + var application: Application? = null val state: dynamic = module.hot?.let { hot -> hot.accept() diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/jsExtra.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/jsExtra.kt similarity index 85% rename from dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/jsExtra.kt rename to dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/jsExtra.kt index 56751272..6354a61c 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/jsExtra.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/js/jsExtra.kt @@ -1,4 +1,7 @@ -package hep.dataforge.vis +package hep.dataforge.js + +@JsName("require") +external fun requireJS(name: String): dynamic inline fun jsObject(builder: T.() -> Unit): T { val obj: T = js("({})") as T diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/editor/bootstrap.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/bootstrap.kt similarity index 89% rename from dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/editor/bootstrap.kt rename to dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/bootstrap.kt index 7be121b1..2f1e4595 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/editor/bootstrap.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/bootstrap.kt @@ -1,4 +1,4 @@ -package hep.dataforge.vis.spatial.editor +package hep.dataforge.vis.js.editor import kotlinx.html.TagConsumer import kotlinx.html.js.div diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt new file mode 100644 index 00000000..f45727b9 --- /dev/null +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt @@ -0,0 +1,74 @@ +package hep.dataforge.vis.js.editor + +import hep.dataforge.names.NameToken +import hep.dataforge.vis.common.VisualGroup +import hep.dataforge.vis.common.VisualObject +import hep.dataforge.vis.common.isEmpty +import kotlinx.html.TagConsumer +import kotlinx.html.dom.append +import kotlinx.html.js.* +import org.w3c.dom.Element +import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLSpanElement +import kotlin.dom.clear + +fun Element.objectTree( + token: NameToken, + obj: VisualObject, + clickCallback: (VisualObject) -> Unit = {} +) { + clear() + append { + card("Object tree") { + subTree(token, obj, clickCallback) + } + } +} + +private fun TagConsumer.subTree( + token: NameToken, + obj: VisualObject, + clickCallback: (VisualObject) -> Unit +) { + + if (obj is VisualGroup && !obj.isEmpty) { + lateinit var toggle: HTMLSpanElement + div("d-inline-block text-truncate") { + toggle = span("objTree-caret") + label("objTree-label") { + +token.toString() + onClickFunction = { clickCallback(obj) } + } + } + val subtree = ul("objTree-subtree") + toggle.onclick = { + toggle.classList.toggle("objTree-caret-down") + subtree.apply { + if (toggle.classList.contains("objTree-caret-down")) { + obj.children.entries + .filter { !it.key.toString().startsWith("@") } + .sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true } + .forEach { (token, child) -> + append { + li().apply { + subTree(token, child, clickCallback) + } + } + } + } else { + this.clear() + } + } + //jQuery(subtree).asDynamic().collapse("toggle") + } + } else { + div("d-inline-block text-truncate") { + span("objTree-leaf") + label("objTree-label") { + +token.toString() + onClickFunction = { clickCallback(obj) } + } + } + } +} + diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsoneditor.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsoneditor.kt new file mode 100644 index 00000000..8bfaf93a --- /dev/null +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsoneditor.kt @@ -0,0 +1,185 @@ +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", + "OVERRIDING_FINAL_MEMBER", + "RETURN_TYPE_MISMATCH_ON_OVERRIDE", + "CONFLICTING_OVERLOADS", + "EXTERNAL_DELEGATION" +) + +package hep.dataforge.vis.js.editor + +import org.w3c.dom.HTMLElement + +external interface Node { + var field: String + var value: String? get() = definedExternally; set(value) = definedExternally + var path: dynamic +} + +external interface NodeName { + var path: Array + var type: dynamic /* 'object' | 'array' */ + var size: Number +} + +external interface ValidationError { + var path: dynamic + var message: String +} + +external interface Template { + var text: String + var title: String + var className: String? get() = definedExternally; set(value) = definedExternally + var field: String + var value: Any +} + +external interface `T$6` { + var startFrom: Number + var options: Array +} + +external interface AutoCompleteOptions { + var confirmKeys: Array? get() = definedExternally; set(value) = definedExternally + var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally +// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally +} + +external interface SelectionPosition { + var row: Number + var column: Number +} + +external interface SerializableNode { + var value: Any + var path: dynamic +} + +external interface Color { + var rgba: Array + var hsla: Array + var rgbString: String + var rgbaString: String + var hslString: String + var hslaString: String + var hex: String +} + +//external interface `T$0` { +// var field: Boolean +// var value: Boolean +//} +// +//external interface `T$1` { +// @nativeGetter +// operator fun get(key: String): String? +// +// @nativeSetter +// operator fun set(key: String, value: String) +//} + +//external interface Languages { +// @nativeGetter +// operator fun get(lang: String): `T$1`? +// +// @nativeSetter +// operator fun set(lang: String, value: `T$1`) +//} + +external interface JSONEditorOptions { +// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally +// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally + var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally + var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally + var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally + var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally + var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally + var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally + var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally + var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally + var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally + var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally + var history: Boolean? get() = definedExternally; set(value) = definedExternally + var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */ + var modes: Array? get() = definedExternally; set(value) = definedExternally + var name: String? get() = definedExternally; set(value) = definedExternally + var schema: Any? get() = definedExternally; set(value) = definedExternally + var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally + var search: Boolean? get() = definedExternally; set(value) = definedExternally + var indentation: Number? get() = definedExternally; set(value) = definedExternally + var theme: String? get() = definedExternally; set(value) = definedExternally + var templates: Array