Compare commits

...

2 Commits

17 changed files with 87 additions and 61 deletions

View File

@ -10,7 +10,7 @@ val dataforgeVersion by extra("0.8.0")
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.4.0" version = "0.4.1-dev-1"
} }
subprojects { subprojects {

View File

@ -1,7 +1,6 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true
kotlin.js.compiler=ir kotlin.js.compiler=ir
#kotlin.incremental.js.ir=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G

View File

@ -23,6 +23,7 @@ kotlin {
dependencies { dependencies {
api("app.softwork:bootstrap-compose:0.1.15") api("app.softwork:bootstrap-compose:0.1.15")
api("app.softwork:bootstrap-compose-icons:0.1.15") api("app.softwork:bootstrap-compose-icons:0.1.15")
implementation(compose.html.svg)
api(compose.html.core) api(compose.html.core)
} }

View File

@ -147,7 +147,7 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.properties.flowChanges().onEach { propertyName -> source.properties.changes.onEach { propertyName ->
val newItem = source.properties.own[propertyName] val newItem = source.properties.own[propertyName]
collector.propertyChanged(name, propertyName, newItem) collector.propertyChanged(name, propertyName, newItem)
}.launchIn(this) }.launchIn(this)

View File

@ -40,7 +40,11 @@ public interface VisionProperties : MetaProvider {
override fun get(name: Name): Meta? = get(name, null, null) override fun get(name: Name): Meta? = get(name, null, null)
public fun flowChanges(): Flow<Name> public val changes: Flow<Name>
@Deprecated("Replace with property", ReplaceWith("changes"))
public fun flowChanges(): Flow<Name> = changes
/** /**
* Notify all listeners that a property has been changed and should be invalidated. * Notify all listeners that a property has been changed and should be invalidated.
@ -64,7 +68,7 @@ public interface MutableVisionProperties : VisionProperties, MutableMetaProvider
public fun set( public fun set(
name: Name, name: Name,
node: Meta?, item: Meta?,
notify: Boolean, notify: Boolean,
) )
@ -186,28 +190,28 @@ public open class AbstractVisionProperties(
return descriptor?.defaultValue return descriptor?.defaultValue
} }
override fun set(name: Name, node: Meta?, notify: Boolean) { override fun set(name: Name, item: Meta?, notify: Boolean) {
//ignore if the value is the same as existing //ignore if the value is the same as existing
if (own[name] == node) return if (own[name] == item) return
if (name.isEmpty()) { if (name.isEmpty()) {
if (node == null) { if (item == null) {
own.items.keys.forEach { own.items.keys.forEach {
remove(it.asName()) remove(it.asName())
} }
} else { } else {
(own.items.keys - node.items.keys).forEach { (own.items.keys - item.items.keys).forEach {
remove(it.asName()) remove(it.asName())
} }
node.items.forEach { (token, item) -> item.items.forEach { (token, item) ->
set(token, item) set(token, item)
} }
} }
} else if (node == null) { } else if (item == null) {
own[name] = node own[name] = item
} else { } else {
own[name] = node own[name] = item
} }
if (notify) { if (notify) {
invalidate(name) invalidate(name)
@ -231,7 +235,8 @@ public open class AbstractVisionProperties(
@Transient @Transient
protected val changesInternal: MutableSharedFlow<Name> = MutableSharedFlow() protected val changesInternal: MutableSharedFlow<Name> = MutableSharedFlow()
override fun flowChanges(): Flow<Name> = changesInternal override val changes: Flow<Name>
get() = changesInternal
override fun invalidate(propertyName: Name) { override fun invalidate(propertyName: Name) {
//send update signal //send update signal

View File

@ -18,7 +18,7 @@ public fun Vision.flowProperty(
): Flow<Meta> = flow { ): Flow<Meta> = flow {
//Pass initial value. //Pass initial value.
emit(properties.get(propertyName, inherit, includeStyles)) emit(properties.get(propertyName, inherit, includeStyles))
properties.flowChanges().collect { name -> properties.changes.collect { name ->
if (name.startsWith(propertyName)) { if (name.startsWith(propertyName)) {
emit(properties.get(propertyName, inherit, includeStyles)) emit(properties.get(propertyName, inherit, includeStyles))
} }
@ -41,7 +41,7 @@ public fun Vision.flowPropertyValue(
): Flow<Value?> = flow { ): Flow<Value?> = flow {
//Pass initial value. //Pass initial value.
emit(properties.getValue(propertyName, inherit, includeStyles)) emit(properties.getValue(propertyName, inherit, includeStyles))
properties.flowChanges().collect { name -> properties.changes.collect { name ->
if (name.startsWith(propertyName)) { if (name.startsWith(propertyName)) {
emit(properties.getValue(propertyName, inherit, includeStyles)) emit(properties.getValue(propertyName, inherit, includeStyles))
} }

View File

@ -25,7 +25,7 @@ public fun Vision.useProperty(
): Job { ): Job {
//Pass initial value. //Pass initial value.
callback(properties.get(propertyName, inherit, includeStyles)) callback(properties.get(propertyName, inherit, includeStyles))
return properties.flowChanges().onEach { name -> return properties.changes.onEach { name ->
if (name.startsWith(propertyName)) { if (name.startsWith(propertyName)) {
callback(properties.get(propertyName, inherit, includeStyles)) callback(properties.get(propertyName, inherit, includeStyles))
} }
@ -47,7 +47,7 @@ public fun <V : Vision, T> V.useProperty(
): Job { ): Job {
//Pass initial value. //Pass initial value.
callback(property.get(this)) callback(property.get(this))
return properties.flowChanges().onEach { name -> return properties.changes.onEach { name ->
if (name.startsWith(property.name.asName())) { if (name.startsWith(property.name.asName())) {
callback(property.get(this@useProperty)) callback(property.get(this@useProperty))
} }
@ -60,7 +60,7 @@ public fun <V : Vision, T> V.useProperty(
public fun Vision.onPropertyChange( public fun Vision.onPropertyChange(
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."), scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
callback: suspend (Name) -> Unit, callback: suspend (Name) -> Unit,
): Job = properties.flowChanges().onEach { ): Job = properties.changes.onEach {
callback(it) callback(it)
}.launchIn(scope) }.launchIn(scope)
@ -71,6 +71,6 @@ public fun <V : Vision, T> V.onPropertyChange(
property: KProperty1<V, T>, property: KProperty1<V, T>,
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."), scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
callback: suspend V.(T) -> Unit, callback: suspend V.(T) -> Unit,
): Job = properties.flowChanges().filter { it.startsWith(property.name.asName()) }.onEach { ): Job = properties.changes.filter { it.startsWith(property.name.asName()) }.onEach {
callback(property.get(this)) callback(property.get(this))
}.launchIn(scope) }.launchIn(scope)

View File

@ -36,6 +36,8 @@ public interface ElementVisionRenderer {
meta: Meta = Meta.EMPTY, meta: Meta = Meta.EMPTY,
) )
override fun toString(): String
public companion object { public companion object {
public const val TYPE: String = "elementVisionRenderer" public const val TYPE: String = "elementVisionRenderer"
public const val ZERO_RATING: Int = 0 public const val ZERO_RATING: Int = 0
@ -66,6 +68,8 @@ public class SingleTypeVisionRenderer<T : Vision>(
renderFunction(name, kClass.cast(vision), meta) renderFunction(name, kClass.cast(vision), meta)
} }
} }
override fun toString(): String = "ElementVisionRender(${kClass.simpleName})"
} }
public inline fun <reified T : Vision> ElementVisionRenderer( public inline fun <reified T : Vision> ElementVisionRenderer(

View File

@ -41,8 +41,7 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
} }
} }
internal val htmlVisionRenderer: ElementVisionRenderer = internal val htmlVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfPlainHtml> { _, vision, _ ->
ElementVisionRenderer<VisionOfPlainHtml> { _, vision, _ ->
div().also { div -> div().also { div ->
div.subscribeToVision(vision) div.subscribeToVision(vision)
vision.useProperty(VisionOfPlainHtml::content) { vision.useProperty(VisionOfPlainHtml::content) {

View File

@ -49,6 +49,8 @@ public actual class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
element.append(div) element.append(div)
} }
override fun toString(): String = "Markup"
override fun content(target: String): Map<Name, Any> = when (target) { override fun content(target: String): Map<Name, Any> = when (target) {
ElementVisionRenderer.TYPE -> mapOf("markup".asName() to this) ElementVisionRenderer.TYPE -> mapOf("markup".asName() to this)
else -> super.content(target) else -> super.content(target)

View File

@ -1,23 +1,17 @@
package space.kscience.visionforge.plotly package space.kscience.visionforge.plotly
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.MutableMetaSerializer
import space.kscience.dataforge.meta.ObservableMeta
import space.kscience.dataforge.meta.asObservable
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.plotly.Plot import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly import space.kscience.plotly.Plotly
import space.kscience.plotly.PlotlyConfig import space.kscience.plotly.PlotlyConfig
import space.kscience.visionforge.AbstractVisionProperties
import space.kscience.visionforge.MutableVisionProperties import space.kscience.visionforge.MutableVisionProperties
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.VisionBuilder
@ -26,36 +20,52 @@ import space.kscience.visionforge.html.VisionOutput
@Serializable @Serializable
@SerialName("vision.plotly") @SerialName("vision.plotly")
public class VisionOfPlotly private constructor( public class VisionOfPlotly private constructor(
@Serializable(MutableMetaSerializer::class) public val meta: MutableMeta, @Serializable(MutableMetaSerializer::class) private val meta: MutableMeta,
) : Vision { ) : Vision {
public constructor(plot: Plot) : this(plot.meta) public constructor(plot: Plot) : this(plot.meta)
public val plot: Plot get() = Plot(meta.asObservable()) @Transient
public val plot: Plot = Plot(meta.asObservable())
@Transient @Transient
override var parent: Vision? = null override var parent: Vision? = null
@Transient @Transient
override val properties: MutableVisionProperties = object : AbstractVisionProperties(this, meta) { override val properties: MutableVisionProperties = object : MutableVisionProperties {
override val own: Meta get() = plot.meta
override fun flowChanges(): Flow<Name> = if (meta is ObservableMeta) { override val changes = callbackFlow {
callbackFlow { plot.meta.onChange(this) {
meta.onChange(this) { println(it)
launch { launch {
send(it) send(it)
} }
} }
awaitClose { awaitClose {
meta.removeListener(this) plot.meta.removeListener(this)
} }
} }
} else emptyFlow()
override fun invalidate(propertyName: Name) { override fun invalidate(propertyName: Name) {
// Do nothing //do nothing, updates to source already counted
// manager?.context?.launch {
// changes.emit(propertyName)
// }
} }
override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? = plot.meta[name]?.value
override fun set(name: Name, item: Meta?, notify: Boolean) {
plot.meta[name] = item
if (notify) invalidate(name)
}
override fun setValue(name: Name, value: Value?, notify: Boolean) {
plot.meta[name] = value
if (notify) invalidate(name)
}
override val descriptor: MetaDescriptor get() = plot.descriptor
} }

View File

@ -33,6 +33,8 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
element.plot(config, plot) element.plot(config, plot)
} }
override fun toString(): String = "Plotly"
override fun content(target: String): Map<Name, Any> = when (target) { override fun content(target: String): Map<Name, Any> = when (target) {
ElementVisionRenderer.TYPE -> mapOf("plotly".asName() to this) ElementVisionRenderer.TYPE -> mapOf("plotly".asName() to this)
else -> super.content(target) else -> super.content(target)

View File

@ -166,16 +166,16 @@ internal class SolidReferenceChild(
includeStyles: Boolean?, includeStyles: Boolean?,
): Value? = own.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles) ): Value? = own.getValue(name) ?: prototype.properties.getValue(name, inherit, includeStyles)
override fun set(name: Name, node: Meta?, notify: Boolean) { override fun set(name: Name, item: Meta?, notify: Boolean) {
own[name] = node own[name] = item
} }
override fun setValue(name: Name, value: Value?, notify: Boolean) { override fun setValue(name: Name, value: Value?, notify: Boolean) {
own.setValue(name, value) own.setValue(name, value)
} }
override fun flowChanges(): Flow<Name> = override val changes: Flow<Name>
owner.properties.flowChanges().filter { it.startsWith(childToken(childName)) } get() = owner.properties.changes.filter { it.startsWith(childToken(childName)) }
override fun invalidate(propertyName: Name) { override fun invalidate(propertyName: Name) {
owner.properties.invalidate(childPropertyName(childName, propertyName)) owner.properties.invalidate(childPropertyName(childName, propertyName))

View File

@ -81,6 +81,8 @@ public class TableVisionJsPlugin : AbstractPlugin(), ElementVisionRenderer {
TabulatorFull(element as HTMLElement, tableOptions) TabulatorFull(element as HTMLElement, tableOptions)
} }
override fun toString(): String = "Table"
override fun content(target: String): Map<Name, Any> = when (target) { override fun content(target: String): Map<Name, Any> = when (target) {
ElementVisionRenderer.TYPE -> mapOf("table".asName() to this) ElementVisionRenderer.TYPE -> mapOf("table".asName() to this)
else -> super.content(target) else -> super.content(target)

View File

@ -89,7 +89,7 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
updatePosition(vision) updatePosition(vision)
//obj.onChildrenChange() //obj.onChildrenChange()
if (observe) { if (observe) {
vision.properties.flowChanges().onEach { name -> vision.properties.changes.onEach { name ->
if ( if (
name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION_KEY) || name.startsWith(Solid.ROTATION_KEY) ||
@ -169,6 +169,8 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
override fun rateVision(vision: Vision): Int = override fun rateVision(vision: Vision): Int =
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
override fun toString(): String = "ThreeJS"
/** /**
* Render the given [Solid] Vision in a [ThreeCanvas] attached * Render the given [Solid] Vision in a [ThreeCanvas] attached
* to the [element]. Canvas objects are cached, so subsequent calls * to the [element]. Canvas objects are cached, so subsequent calls

View File

@ -147,7 +147,7 @@ public fun ThreeView(
} }
}, },
name = Name.EMPTY, name = Name.EMPTY,
updates = vision.properties.flowChanges(), updates = vision.properties.changes,
rootDescriptor = vision.descriptor rootDescriptor = vision.descriptor
) )
vision.styles.takeIf { it.isNotEmpty() }?.let { styles -> vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->

View File

@ -32,8 +32,8 @@ kscience {
jsMain { jsMain {
api(projects.visionforgeThreejs) api(projects.visionforgeThreejs)
implementation(npm("file-saver", "2.0.5")) api(npm("file-saver", "2.0.5"))
implementation(npm("@types/file-saver", "2.0.7")) api(npm("@types/file-saver", "2.0.7"))
compileOnly(npm("webpack-bundle-analyzer", "4.5.0")) compileOnly(npm("webpack-bundle-analyzer", "4.5.0"))
} }
} }