Compare commits

...

5 Commits
master ... dev

18 changed files with 75 additions and 27 deletions

View File

@ -11,6 +11,7 @@
### Removed
### Fixed
- Fix the problem where property listeners do not react on property child node changa
### Security

View File

@ -11,7 +11,7 @@ val dataforgeVersion by extra("0.10.1")
allprojects {
group = "space.kscience"
version = "0.5.0"
version = "0.5.1-dev-1"
}
subprojects {

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -20,7 +20,7 @@ kotlin{
// A workaround for https://youtrack.jetbrains.com/issue/KT-44101
val copyPlotlyResources by tasks.creating(Copy::class){
val copyPlotlyResources by tasks.registering(Copy::class){
dependsOn(":plotly-kt:plotly-kt-server:jvmProcessResources")
mustRunAfter(":plotly-kt:plotly-kt-server:jvmTestProcessResources")
from(project(":plotly-kt:plotly-kt-server").layout.buildDirectory.file("processedResources/jvm/main"))

View File

@ -80,8 +80,9 @@ public class Plot : AbstractVision(), MutableVisionGroup<Trace> {
*/
@UnstablePlotlyAPI
@JvmSynchronized
internal fun removeTrace(index: Int) {
public fun removeTrace(index: Int) {
_data.removeAt(index)
emitEvent(VisionGroupCompositionChangedEvent(NameToken("trace", _data.size.toString()), null))
}
override val descriptor: MetaDescriptor get() = Companion.descriptor

View File

@ -8,9 +8,11 @@ import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.request
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.plotly.models.Trace
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.html.*
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.html.VisionTagConsumer
import kotlin.js.JsName
/**
@ -65,6 +67,18 @@ public class PlotlyConfig : Scheme() {
public var responsive: Boolean? by boolean()
public var imageFormat: String? by string(Name.parse("toImageButtonOptions.format"))
/**
* A list of class names applied to the output `div` in the generated HTML for the plot.
*
* This property allows customization of the CSS classes assigned to the `div` element
* that contains the rendered plot. It can be utilized to add custom styling or specific
* class-based behaviors to the output.
*
* By default, this property is initialized as an empty list and can be updated to include
* necessary class names as strings.
*/
public var classes: List<String> by stringList(default = emptyArray(), key = VisionTagConsumer.OUTPUT_DIV_CLASSES_KEY.asName())
public fun withEditorButton() {
showEditInChartStudio = true
plotlyServerURL = "https://chart-studio.plotly.com"

View File

@ -1,5 +1,6 @@
package space.kscience.plotly
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.filterIsInstance
@ -32,7 +33,11 @@ private fun List<MetaRepr>.toDynamic(): Array<dynamic> = map { it.toDynamic() }.
* Attach a plot to this element or update the existing plot
*/
@OptIn(DelicateCoroutinesApi::class)
public fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plot: Plot) {
public fun Element.plot(
plotlyConfig: PlotlyConfig,
plot: Plot,
scope: CoroutineScope = plot.manager?.context ?: GlobalScope
) {
// console.info("""
// Plotly.react(
@ -51,7 +56,7 @@ public fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plot: Plot)
)
//start updates
val listenJob = (plot.manager?.context ?: GlobalScope).launch {
val listenJob = scope.launch {
plot.data.forEachIndexed { index, trace ->
trace.eventFlow.filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
val traceData = trace.toDynamic()
@ -92,27 +97,34 @@ public fun Element.plot(plot: Plot, plotlyConfig: PlotlyConfig = PlotlyConfig())
/**
* Create a plot in this element
*/
public inline fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plotBuilder: Plot.() -> Unit) {
plot(plotlyConfig, Plot().apply(plotBuilder))
public inline fun Element.plot(
scope: CoroutineScope,
plotlyConfig: PlotlyConfig = PlotlyConfig(),
plotBuilder: Plot.() -> Unit
) {
plot(plotlyConfig, Plot().apply(plotBuilder), scope)
}
public class PlotlyElement(public val div: HTMLElement)
/**
* Create a div element and render plot in it
* Create a div element and render the plot in it
*/
@OptIn(DelicateCoroutinesApi::class)
public fun TagConsumer<HTMLElement>.plotDiv(
plotlyConfig: PlotlyConfig = PlotlyConfig(),
plotlyConfig: PlotlyConfig,
plot: Plot,
scope: CoroutineScope = plot.manager?.context ?: GlobalScope,
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(plotlyConfig, plot) })
/**
* Render plot in the HTML element using direct plotly API.
*/
public inline fun TagConsumer<HTMLElement>.plotDiv(
scope: CoroutineScope,
plotlyConfig: PlotlyConfig = PlotlyConfig(),
plotBuilder: Plot.() -> Unit,
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(plotlyConfig, plotBuilder) })
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(scope, plotlyConfig, plotBuilder) })
@OptIn(ExperimentalSerializationApi::class)
public fun PlotlyElement.on(eventType: PlotlyEventListenerType, block: MouseEvent.(PlotlyEvent) -> Unit) {

View File

@ -2,6 +2,7 @@ plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
`maven-publish`
}
kscience {
@ -26,6 +27,7 @@ kscience {
jvmMain {
api(projects.visionforgeServer)
api(project.dependencies.platform(spclibs.ktor.bom))
api("io.ktor:ktor-server-cio")
}
}

View File

@ -22,7 +22,7 @@ kotlin {
jsMain {
dependencies {
api("app.softwork:bootstrap-compose:0.3.0")
api("app.softwork:bootstrap-compose:0.3.1")
api("app.softwork:bootstrap-compose-icons:0.3.0")
// implementation(npm("bootstrap", "5.3.3"))
// implementation(npm(" bootstrap-icons", "1.11.3"))

View File

@ -28,9 +28,8 @@ public interface ComposeHtmlVisionRenderer : ElementVisionRenderer {
public companion object
}
/**
* A compose-html renderer for a vision of given type
* A compose-html renderer for a vision of a given type
*/
public inline fun <reified T : Vision> ComposeHtmlVisionRenderer(
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,

View File

@ -0,0 +1,18 @@
package space.kscience.visionforge.html
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.flow.map
import space.kscience.visionforge.Vision
import space.kscience.visionforge.flowProperty
import kotlin.reflect.KProperty1
@Composable
public fun <V : Vision, T> V.collectPropertyAsState(
property: KProperty1<V, T>,
propertyName: String = property.name,
): State<T> = flowProperty(propertyName)
.map { property.get(this@collectPropertyAsState) }
.collectAsState(property.get(this))

View File

@ -118,7 +118,7 @@ public interface MutableVision : Vision {
override suspend fun receiveEvent(event: VisionEvent) {
if (event is VisionChange) {
if (event.children?.isNotEmpty() == true) {
error("Vision is not a group")
error("Received vision group change event, but $this Vision does not handle children changes")
}
event.properties?.let {
updateProperties(it, Name.EMPTY)
@ -130,7 +130,7 @@ public interface MutableVision : Vision {
name: Name,
inherited: Boolean = isInheritedProperty(name),
useStyles: Boolean = isStyledProperty(name),
): MutableMeta = properties.getOrCreate(name).withDefault { suffix->
): MutableMeta = properties.getOrCreate(name).withDefault { suffix ->
val propertyName = name + suffix
if (useStyles) getStyleProperty(propertyName)?.let { return@withDefault it }
if (inherited) parent?.readProperty(propertyName, inherited, useStyles)?.let { return@withDefault it }

View File

@ -14,7 +14,6 @@ import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.html.VisionTagConsumer.Companion.DEFAULT_VISION_NAME
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
import kotlin.collections.set
@DslMarker
public annotation class VisionDSL
@ -23,7 +22,7 @@ public annotation class VisionDSL
* A placeholder object to attach inline vision builders.
*/
@VisionDSL
public class VisionOutput(override val context: Context, public val name: Name): ContextAware {
public class VisionOutput(override val context: Context, public val name: Name) : ContextAware {
public var meta: Meta = Meta.EMPTY
private val requirements: MutableSet<PluginFactory<*>> = HashSet()
@ -89,7 +88,8 @@ public abstract class VisionTagConsumer<R>(
+"Empty Vision output"
} else div {
id = resolveId(name)
classes = setOf(OUTPUT_CLASS)
classes = setOf(OUTPUT_CLASS, *(outputMeta[OUTPUT_DIV_CLASSES_KEY].stringList?.toTypedArray() ?: emptyArray()))
if (vision.parent == null) {
vision.setAsRoot(manager)
}
@ -155,6 +155,8 @@ public abstract class VisionTagConsumer<R>(
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
public const val OUTPUT_DIV_CLASSES_KEY: String = "classes"
public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch"
public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect"

View File

@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.Value
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
import kotlin.reflect.KProperty1
private fun Vision.withAncestors(): List<Vision> = buildList {
@ -42,7 +43,7 @@ public fun Vision.flowProperty(
}
combinedFlow.filterIsInstance<VisionPropertyChangedEvent>().collect { event ->
if (event.propertyName == propertyName || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
if (event.propertyName.startsWith(propertyName) || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
emit(readProperty(event.propertyName, inherited, useStyles))
}
}
@ -89,7 +90,7 @@ public fun Vision.useProperty(
} else {
eventFlow
}.filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
if (event.propertyName == propertyName || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
if (event.propertyName.startsWith(propertyName) || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
callback(readProperty(event.propertyName, inherited, useStyles))
}
}.collect()
@ -134,7 +135,7 @@ public fun <V : Vision, T> V.onPropertyChange(
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
callback: suspend V.(T) -> Unit,
): Job = inheritedEventFlow().filterIsInstance<VisionPropertyChangedEvent>().onEach {
if (it.propertyName.toString() == property.name) {
if (it.propertyName.startsWith(property.name)) {
callback(property.get(this))
}
}.launchIn(scope)

View File

@ -73,5 +73,6 @@ internal class PropertyFlowTest {
collectorJob.cancel()
assertEquals(listOf(22, 11, 33), collectedValues)
println("finished")
}
}

View File

@ -2,7 +2,7 @@ plugins {
id("space.kscience.gradle.mpp")
}
val kmathVersion = "0.4.1"
val kmathVersion = "0.4.2"
kscience {
jvm()

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.three
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.visionforge.html.*
import space.kscience.visionforge.solid.Solids
import java.awt.Desktop
@ -9,7 +8,6 @@ import java.nio.file.Path
public val VisionPage.Companion.threeJsHeader: HtmlFragment get() = scriptHeader("js/visionforge-three.js")
@DFExperimental
public fun Solids.makeThreeJsFile(
path: Path? = null,
title: String = "VisionForge page",

View File

@ -9,7 +9,6 @@ import kotlin.test.Test
class TestServerExtensions {
@Suppress("UNUSED_VARIABLE")
@Test
fun testServerHeader(){
val string = createHTML().apply {