forked from kscience/visionforge
Refactor pages
This commit is contained in:
parent
960d17855b
commit
3c51060e2e
@ -6,6 +6,7 @@
|
||||
- MeshLine for thick lines
|
||||
|
||||
### Changed
|
||||
- API update for server and pages
|
||||
- Edges moved to solids module for easier construction
|
||||
- Visions **must** be rooted in order to subscribe to updates.
|
||||
- Visions use flows instead of direct subscriptions.
|
||||
|
@ -1,3 +1,7 @@
|
||||
import space.kscience.gradle.isInDevelopment
|
||||
import space.kscience.gradle.useApache2Licence
|
||||
import space.kscience.gradle.useSPCTeam
|
||||
|
||||
plugins {
|
||||
id("space.kscience.gradle.project")
|
||||
// id("org.jetbrains.kotlinx.kover") version "0.5.0"
|
||||
@ -6,9 +10,9 @@ plugins {
|
||||
val dataforgeVersion by extra("0.6.0-dev-15")
|
||||
val fxVersion by extra("11")
|
||||
|
||||
allprojects{
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.3.0-dev-2"
|
||||
version = "0.3.0-dev-3"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@ -21,16 +25,26 @@ subprojects {
|
||||
maven("https://maven.jzy3d.org/releases")
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>{
|
||||
kotlinOptions{
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ksciencePublish {
|
||||
github("visionforge")
|
||||
space()
|
||||
pom("https://github.com/SciProgCentre/visionforge") {
|
||||
useApache2Licence()
|
||||
useSPCTeam()
|
||||
}
|
||||
github(githubProject = "visionforge", githubOrg = "SciProgCentre")
|
||||
space(
|
||||
if (isInDevelopment) {
|
||||
"https://maven.pkg.jetbrains.space/spc/p/sci/dev"
|
||||
} else {
|
||||
"https://maven.pkg.jetbrains.space/spc/p/sci/maven"
|
||||
}
|
||||
)
|
||||
sonatype()
|
||||
}
|
||||
|
||||
@ -39,8 +53,3 @@ apiValidation {
|
||||
}
|
||||
|
||||
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
|
||||
|
||||
|
||||
//rootProject.extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
|
||||
// versions.webpackCli.version = "4.10.0"
|
||||
//}
|
@ -218,6 +218,7 @@ private object RootDecoder {
|
||||
|
||||
var value: Any? = null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> getOrPutValue(builder: (JsonElement) -> T): T {
|
||||
if (value == null) {
|
||||
value = builder(element)
|
||||
|
@ -4,7 +4,7 @@ import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.fetch
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.formFragment
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
import space.kscience.visionforge.server.close
|
||||
@ -15,7 +15,7 @@ fun main() {
|
||||
val visionManager = Global.fetch(VisionManager)
|
||||
|
||||
val server = visionManager.serve {
|
||||
page(header = Page.scriptHeader("js/visionforge-playground.js")) {
|
||||
page(VisionPage.scriptHeader("js/visionforge-playground.js")) {
|
||||
val form = formFragment("form") {
|
||||
label {
|
||||
htmlFor = "fname"
|
||||
|
@ -1,15 +1,92 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.plotly.scatter
|
||||
import space.kscience.dataforge.meta.Value
|
||||
import space.kscience.plotly.layout
|
||||
import space.kscience.plotly.models.*
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.plotly.plotly
|
||||
|
||||
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
|
||||
vision {
|
||||
val trace1 = Violin {
|
||||
text("sample length: 32")
|
||||
marker {
|
||||
line {
|
||||
width = 2
|
||||
color("#bebada")
|
||||
}
|
||||
symbol = Symbol.valueOf("line-ns")
|
||||
}
|
||||
orientation = Orientation.h
|
||||
hoveron = ViolinHoveron.`points+kde`
|
||||
meanline {
|
||||
visible = true
|
||||
}
|
||||
legendgroup = "F"
|
||||
scalegroup = "F"
|
||||
points = ViolinPoints.all
|
||||
pointpos = 1.2
|
||||
jitter = 0
|
||||
box {
|
||||
visible = true
|
||||
}
|
||||
scalemode = ViolinScaleMode.count
|
||||
showlegend = false
|
||||
side = ViolinSide.positive
|
||||
y0 = Value.of(0)
|
||||
line {
|
||||
color("#bebada")
|
||||
}
|
||||
name = "F"
|
||||
|
||||
x(10.07, 34.83, 10.65, 12.43, 24.08, 13.42, 12.48, 29.8, 14.52, 11.38,
|
||||
20.27, 11.17, 12.26, 18.26, 8.51, 10.33, 14.15, 13.16, 17.47, 27.05, 16.43,
|
||||
8.35, 18.64, 11.87, 19.81, 43.11, 13.0, 12.74, 13.0, 16.4, 16.47, 18.78)
|
||||
}
|
||||
|
||||
val trace2 = Violin {
|
||||
text("sample length: 32")
|
||||
marker {
|
||||
line {
|
||||
width = 2
|
||||
color("#8dd3c7")
|
||||
}
|
||||
symbol = Symbol.valueOf("line-ns")
|
||||
}
|
||||
orientation = Orientation.h
|
||||
hoveron = ViolinHoveron.`points+kde`
|
||||
meanline {
|
||||
visible = true
|
||||
}
|
||||
legendgroup = "M"
|
||||
scalegroup = "M"
|
||||
points = ViolinPoints.all
|
||||
pointpos = -1.2
|
||||
jitter = 0
|
||||
box {
|
||||
visible = true
|
||||
}
|
||||
scalemode = ViolinScaleMode.count
|
||||
showlegend = false
|
||||
side = ViolinSide.negative
|
||||
y0 = Value.of(0)
|
||||
|
||||
line {
|
||||
color("#8dd3c7")
|
||||
}
|
||||
name = "M"
|
||||
|
||||
x(27.2, 22.76, 17.29, 19.44, 16.66, 32.68, 15.98, 13.03, 18.28, 24.71,
|
||||
21.16, 11.69, 14.26, 15.95, 8.52, 22.82, 19.08, 16.0, 34.3, 41.19, 9.78,
|
||||
7.51, 28.44, 15.48, 16.58, 7.56, 10.34, 13.51, 18.71, 20.53)
|
||||
}
|
||||
|
||||
plotly {
|
||||
scatter {
|
||||
x(1, 2, 3)
|
||||
y(5, 8, 7)
|
||||
traces(trace1, trace2)
|
||||
layout {
|
||||
width = 800
|
||||
height = 800
|
||||
title = "Advanced Violin Plot"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.importScriptHeader
|
||||
import space.kscience.visionforge.makeFile
|
||||
import java.awt.Desktop
|
||||
@ -16,10 +16,10 @@ public fun makeVisionFile(
|
||||
show: Boolean = true,
|
||||
content: HtmlVisionFragment,
|
||||
): Unit {
|
||||
val actualPath = Page(Global, content = content).makeFile(path) { actualPath ->
|
||||
val actualPath = VisionPage(Global, content = content).makeFile(path) { actualPath ->
|
||||
mapOf(
|
||||
"title" to Page.title(title),
|
||||
"playground" to Page.importScriptHeader("js/visionforge-playground.js", resourceLocation, actualPath),
|
||||
"title" to VisionPage.title(title),
|
||||
"playground" to VisionPage.importScriptHeader("js/visionforge-playground.js", resourceLocation, actualPath),
|
||||
)
|
||||
}
|
||||
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
|
@ -10,8 +10,8 @@ import space.kscience.dataforge.meta.Null
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.plus
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.server.DataServeMode
|
||||
import space.kscience.visionforge.server.close
|
||||
import space.kscience.visionforge.server.openInBrowser
|
||||
import space.kscience.visionforge.server.serve
|
||||
@ -37,7 +37,8 @@ fun main() {
|
||||
}
|
||||
|
||||
val server = satContext.visionManager.serve {
|
||||
page(header = Page.threeJsHeader + Page.styleSheetHeader("css/styles.css")) {
|
||||
dataMode = DataServeMode.UPDATE
|
||||
page(VisionPage.threeJsHeader, VisionPage.styleSheetHeader("css/styles.css")) {
|
||||
div("flex-column") {
|
||||
h1 { +"Satellite detector demo" }
|
||||
vision { sat }
|
||||
|
@ -6,4 +6,4 @@ kotlin.incremental.js.ir=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
|
||||
toolsVersion=0.12.0-kotlin-1.7.20-Beta
|
||||
toolsVersion=0.13.3-kotlin-1.7.20
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -49,7 +49,7 @@ public abstract class JupyterPluginBase(final override val context: Context) : J
|
||||
|
||||
}
|
||||
|
||||
render<Page> { page ->
|
||||
render<VisionPage> { page ->
|
||||
HTML(page.render(createHTML()), true)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import kotlinx.css.padding
|
||||
import kotlinx.css.properties.border
|
||||
import kotlinx.css.px
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.files.BlobPropertyBag
|
||||
import react.FC
|
||||
|
@ -3,7 +3,6 @@ package space.kscience.visionforge.bootstrap
|
||||
import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.client.createRoot
|
||||
import react.key
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.isEmpty
|
||||
import space.kscience.visionforge.Vision
|
||||
|
@ -3,7 +3,7 @@ package space.kscience.visionforge.react
|
||||
import kotlinx.css.Align
|
||||
import kotlinx.css.alignItems
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.a
|
||||
import react.dom.attrs
|
||||
|
@ -1,10 +1,10 @@
|
||||
package space.kscience.visionforge.react
|
||||
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import org.w3c.dom.HTMLOptionElement
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.asList
|
||||
import org.w3c.dom.events.Event
|
||||
import react.FC
|
||||
import react.dom.attrs
|
||||
import react.dom.option
|
||||
|
@ -10,7 +10,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.TextDecoration
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.attrs
|
||||
import space.kscience.dataforge.meta.*
|
||||
|
@ -4,8 +4,8 @@ import kotlinx.css.pct
|
||||
import kotlinx.css.width
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.FC
|
||||
import react.dom.attrs
|
||||
import react.fc
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.css.cursor
|
||||
import kotlinx.css.properties.TextDecorationLine
|
||||
import kotlinx.css.properties.textDecoration
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.attrs
|
||||
import space.kscience.dataforge.names.Name
|
||||
|
@ -7,9 +7,9 @@ import kotlinx.css.width
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onKeyDownFunction
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.FC
|
||||
import react.Props
|
||||
import react.dom.attrs
|
||||
|
@ -4,7 +4,6 @@ import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.client.createRoot
|
||||
import react.dom.p
|
||||
import react.key
|
||||
import ringui.Island
|
||||
import ringui.SmartTabs
|
||||
import ringui.Tab
|
||||
|
@ -8,7 +8,7 @@ import kotlinx.css.padding
|
||||
import kotlinx.css.properties.border
|
||||
import kotlinx.css.px
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.events.Event
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.files.BlobPropertyBag
|
||||
import react.FC
|
||||
|
@ -9,7 +9,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api("space.kscience:dataforge-context:$dataforgeVersion")
|
||||
api(npmlibs.kotlinx.html)
|
||||
api("org.jetbrains.kotlinx:kotlinx-html:0.8.0")
|
||||
api("org.jetbrains.kotlin-wrappers:kotlin-css")
|
||||
}
|
||||
}
|
||||
|
@ -118,14 +118,14 @@ private fun CoroutineScope.collectChange(
|
||||
name: Name,
|
||||
source: Vision,
|
||||
mutex: Mutex,
|
||||
collector: () -> VisionChangeBuilder,
|
||||
collector: VisionChangeBuilder,
|
||||
) {
|
||||
|
||||
//Collect properties change
|
||||
source.onPropertyChange(this) { propertyName ->
|
||||
source.properties.changes.onEach { propertyName ->
|
||||
val newItem = source.properties.own?.get(propertyName)
|
||||
collector().propertyChanged(name, propertyName, newItem)
|
||||
}
|
||||
collector.propertyChanged(name, propertyName, newItem)
|
||||
}.launchIn(this)
|
||||
|
||||
val children = source.children
|
||||
//Subscribe for children changes
|
||||
@ -141,7 +141,7 @@ private fun CoroutineScope.collectChange(
|
||||
collectChange(fullName, after, mutex, collector)
|
||||
}
|
||||
mutex.withLock {
|
||||
collector().setChild(fullName, after)
|
||||
collector.setChild(fullName, after)
|
||||
}
|
||||
}?.launchIn(this)
|
||||
}
|
||||
@ -156,7 +156,7 @@ public fun Vision.flowChanges(
|
||||
coroutineScope {
|
||||
val collector = VisionChangeBuilder()
|
||||
val mutex = Mutex()
|
||||
collectChange(Name.EMPTY, this@flowChanges, mutex) { collector }
|
||||
collectChange(Name.EMPTY, this@flowChanges, mutex, collector)
|
||||
|
||||
//Send initial vision state
|
||||
val initialChange = VisionChange(vision = deepCopy(manager))
|
||||
@ -167,10 +167,10 @@ public fun Vision.flowChanges(
|
||||
delay(collectionDuration)
|
||||
//Propagate updates only if something is changed
|
||||
if (!collector.isEmpty()) {
|
||||
//emit changes
|
||||
emit(collector.deepCopy(manager))
|
||||
//Reset the collector
|
||||
mutex.withLock {
|
||||
//emit changes
|
||||
emit(collector.deepCopy(manager))
|
||||
//Reset the collector
|
||||
collector.reset()
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,14 @@ package space.kscience.visionforge.html
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
|
||||
public data class Page(
|
||||
/**
|
||||
* A structure representing a single page with Visions to be rendered.
|
||||
*
|
||||
* @param pageHeaders static headers for this page.
|
||||
*/
|
||||
public data class VisionPage(
|
||||
public val context: Context,
|
||||
public val headers: Map<String, HtmlFragment> = emptyMap(),
|
||||
public val pageHeaders: Map<String, HtmlFragment> = emptyMap(),
|
||||
public val content: HtmlVisionFragment,
|
||||
) {
|
||||
public fun <R> render(root: TagConsumer<R>): R = root.apply {
|
||||
@ -13,7 +18,7 @@ public data class Page(
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
}
|
||||
headers.values.forEach {
|
||||
pageHeaders.values.forEach {
|
||||
fragment(it)
|
||||
}
|
||||
}
|
@ -114,7 +114,7 @@ internal fun fileCssHeader(
|
||||
/**
|
||||
* Make a script header from a resource file, automatically copying file to appropriate location
|
||||
*/
|
||||
public fun Page.Companion.importScriptHeader(
|
||||
public fun VisionPage.Companion.importScriptHeader(
|
||||
scriptResource: String,
|
||||
resourceLocation: ResourceLocation,
|
||||
htmlPath: Path? = null,
|
||||
|
@ -3,7 +3,7 @@ package space.kscience.visionforge
|
||||
import kotlinx.html.stream.createHTML
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.Page
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import java.awt.Desktop
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
@ -54,8 +54,11 @@ import java.nio.file.Path
|
||||
// }
|
||||
//}
|
||||
|
||||
/**
|
||||
* Export a [VisionPage] to a file
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun Page.makeFile(
|
||||
public fun VisionPage.makeFile(
|
||||
path: Path?,
|
||||
defaultHeaders: ((Path) -> Map<String, HtmlFragment>)? = null,
|
||||
): Path {
|
||||
@ -64,7 +67,7 @@ public fun Page.makeFile(
|
||||
} ?: Files.createTempFile("tempPlot", ".html")
|
||||
|
||||
val actualDefaultHeaders = defaultHeaders?.invoke(actualFile)
|
||||
val actualPage = if (actualDefaultHeaders == null) this else copy(headers = actualDefaultHeaders + headers)
|
||||
val actualPage = if (actualDefaultHeaders == null) this else copy(pageHeaders = actualDefaultHeaders + pageHeaders)
|
||||
|
||||
val htmlString = actualPage.render(createHTML())
|
||||
|
||||
@ -73,7 +76,7 @@ public fun Page.makeFile(
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun Page.show(path: Path? = null) {
|
||||
public fun VisionPage.show(path: Path? = null) {
|
||||
val actualPath = makeFile(path)
|
||||
Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
|
||||
public expect class MarkupPlugin: VisionPlugin
|
@ -16,7 +16,7 @@ import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FOR
|
||||
import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
public actual class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
public val visionClient: VisionClient by require(VisionClient)
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
override val visionSerializersModule: SerializersModule get() = markupSerializersModule
|
||||
|
@ -0,0 +1,24 @@
|
||||
package space.kscience.visionforge.markup
|
||||
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public actual class MarkupPlugin : VisionPlugin() {
|
||||
override val visionSerializersModule: SerializersModule get() = markupSerializersModule
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
public companion object : PluginFactory<MarkupPlugin> {
|
||||
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override val type: KClass<out MarkupPlugin> = MarkupPlugin::class
|
||||
|
||||
override fun build(context: Context, meta: Meta): MarkupPlugin = MarkupPlugin()
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package space.kscience.visionforge.server
|
||||
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.application.log
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.ApplicationEngine
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
@ -24,7 +27,6 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.createHTML
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionChange
|
||||
@ -32,6 +34,7 @@ import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.flowChanges
|
||||
import space.kscience.visionforge.html.HtmlFragment
|
||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.visionFragment
|
||||
import space.kscience.visionforge.server.VisionServer.Companion.DEFAULT_PAGE
|
||||
import java.awt.Desktop
|
||||
@ -39,6 +42,23 @@ import java.net.URI
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
||||
public enum class DataServeMode {
|
||||
/**
|
||||
* Embed the initial state of the vision inside its html tag.
|
||||
*/
|
||||
EMBED,
|
||||
|
||||
/**
|
||||
* Fetch data on vision load. Do not embed data.
|
||||
*/
|
||||
FETCH,
|
||||
|
||||
/**
|
||||
* Connect to server to get pushes. The address of the server is embedded in the tag.
|
||||
*/
|
||||
UPDATE
|
||||
}
|
||||
|
||||
/**
|
||||
* A ktor plugin container with given [routing]
|
||||
* @param serverUrl a server url including root route
|
||||
@ -63,25 +83,20 @@ public class VisionServer internal constructor(
|
||||
*/
|
||||
public var cacheFragments: Boolean by meta.boolean(true)
|
||||
|
||||
/**
|
||||
* Embed the initial state of the vision inside its html tag. Default: `true`
|
||||
*/
|
||||
public var dataEmbed: Boolean by meta.boolean(true, Name.parse("data.embed"))
|
||||
public var dataMode: DataServeMode by meta.enum(DataServeMode.UPDATE)
|
||||
|
||||
private val serverHeaders: MutableMap<String, HtmlFragment> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Fetch data on vision load. Overrides embedded data. Default: `false`
|
||||
* Set up a default header that is automatically added to all pages on this server
|
||||
*/
|
||||
public var dataFetch: Boolean by meta.boolean(false, Name.parse("data.fetch"))
|
||||
|
||||
/**
|
||||
* Connect to server to get pushes. The address of the server is embedded in the tag. Default: `true`
|
||||
*/
|
||||
public var dataUpdate: Boolean by meta.boolean(true, Name.parse("data.update"))
|
||||
public fun header(key: String, block: HtmlFragment) {
|
||||
serverHeaders[key] = block
|
||||
}
|
||||
|
||||
private fun HTML.visionPage(
|
||||
title: String,
|
||||
pagePath: String,
|
||||
header: HtmlFragment,
|
||||
headers: Map<String, HtmlFragment>,
|
||||
visionFragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
var visionMap: Map<Name, Vision>? = null
|
||||
@ -89,17 +104,18 @@ public class VisionServer internal constructor(
|
||||
head {
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
header()
|
||||
}
|
||||
title(title)
|
||||
consumer.header()
|
||||
(serverHeaders + headers).values.forEach {
|
||||
consumer.it()
|
||||
}
|
||||
}
|
||||
body {
|
||||
//Load the fragment and remember all loaded visions
|
||||
visionMap = visionFragment(
|
||||
context = visionManager.context,
|
||||
embedData = true,
|
||||
fetchUpdatesUrl = "$serverUrl$pagePath/ws",
|
||||
embedData = dataMode == DataServeMode.EMBED,
|
||||
fetchDataUrl = if (dataMode != DataServeMode.EMBED) "$serverUrl$pagePath/data" else null,
|
||||
fetchUpdatesUrl = if (dataMode == DataServeMode.UPDATE) "$serverUrl$pagePath/ws" else null,
|
||||
fragment = visionFragment
|
||||
)
|
||||
}
|
||||
@ -110,7 +126,6 @@ public class VisionServer internal constructor(
|
||||
/**
|
||||
* Server a map of visions without providing explicit html page for them
|
||||
*/
|
||||
@OptIn(DFExperimental::class)
|
||||
private fun serveVisions(route: Route, visions: Map<Name, Vision>): Unit = route {
|
||||
application.log.info("Serving visions $visions at $route")
|
||||
|
||||
@ -125,7 +140,7 @@ public class VisionServer internal constructor(
|
||||
val data = it.data.decodeToString()
|
||||
application.log.debug("Received update: \n$data")
|
||||
val change = visionManager.jsonFormat.decodeFromString(
|
||||
VisionChange.serializer(),data
|
||||
VisionChange.serializer(), data
|
||||
)
|
||||
vision.update(change)
|
||||
}
|
||||
@ -133,7 +148,7 @@ public class VisionServer internal constructor(
|
||||
|
||||
try {
|
||||
withContext(visionManager.context.coroutineContext) {
|
||||
vision.flowChanges(updateInterval.milliseconds).onEach { update ->
|
||||
vision.flowChanges(updateInterval.milliseconds).onEach { update ->
|
||||
val json = visionManager.jsonFormat.encodeToString(
|
||||
VisionChange.serializer(),
|
||||
update
|
||||
@ -191,12 +206,11 @@ public class VisionServer internal constructor(
|
||||
}.finalize()
|
||||
|
||||
/**
|
||||
* Serve a page, potentially containing any number of visions at a given [pagePath] with given [headers].
|
||||
* Serve a page, potentially containing any number of visions at a given [route] with given [header].
|
||||
*/
|
||||
public fun page(
|
||||
pagePath: String = DEFAULT_PAGE,
|
||||
title: String = "VisionForge server page '$pagePath'",
|
||||
header: HtmlFragment = {},
|
||||
route: String = DEFAULT_PAGE,
|
||||
headers: Map<String, HtmlFragment>,
|
||||
visionFragment: HtmlVisionFragment,
|
||||
) {
|
||||
val visions = HashMap<Name, Vision>()
|
||||
@ -204,13 +218,13 @@ public class VisionServer internal constructor(
|
||||
val cachedHtml: String? = if (cacheFragments) {
|
||||
//Create and cache page html and map of visions
|
||||
createHTML(true).html {
|
||||
visions.putAll(visionPage(title, pagePath, header, visionFragment))
|
||||
visions.putAll(visionPage(route, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
root.route(pagePath) {
|
||||
root.route(route) {
|
||||
serveVisions(this, visions)
|
||||
//filled pages
|
||||
get {
|
||||
@ -218,7 +232,7 @@ public class VisionServer internal constructor(
|
||||
//re-create html and vision list on each call
|
||||
call.respondHtml {
|
||||
visions.clear()
|
||||
visions.putAll(visionPage(title, pagePath, header, visionFragment))
|
||||
visions.putAll(visionPage(route, headers, visionFragment))
|
||||
}
|
||||
} else {
|
||||
//Use cached html
|
||||
@ -226,8 +240,22 @@ public class VisionServer internal constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun page(
|
||||
vararg headers: HtmlFragment,
|
||||
route: String = DEFAULT_PAGE,
|
||||
title: String = "VisionForge server page '$route'",
|
||||
visionFragment: HtmlVisionFragment,
|
||||
) {
|
||||
page(route, mapOf("title" to VisionPage.title(title)) + headers.associateBy { it.hashCode().toString() }, visionFragment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render given [VisionPage] at server
|
||||
*/
|
||||
public fun page(route: String, page: VisionPage) {
|
||||
page(route = route, headers = page.pageHeaders, visionFragment = page.content)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -3,6 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
kotlin{
|
||||
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Disabled
|
||||
js{
|
||||
binaries.library()
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
|
||||
|
||||
override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D {
|
||||
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||
canvas.width = 200
|
||||
canvas.height = 200
|
||||
|
||||
canvas.getContext("2d").apply {
|
||||
this as CanvasRenderingContext2D
|
||||
font = "Bold ${vision.fontSize}pt ${vision.fontFamily}"
|
||||
@ -43,15 +46,13 @@ public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
|
||||
val texture = Texture(canvas)
|
||||
texture.needsUpdate = true
|
||||
|
||||
val material = MeshBasicMaterial().apply {
|
||||
map = texture
|
||||
side = DoubleSide
|
||||
transparent = true
|
||||
}
|
||||
|
||||
val mesh = Mesh(
|
||||
PlaneGeometry(canvas.width, canvas.height),
|
||||
material
|
||||
MeshBasicMaterial().apply {
|
||||
map = texture
|
||||
side = DoubleSide
|
||||
transparent = true
|
||||
}
|
||||
)
|
||||
|
||||
mesh.updatePosition(vision)
|
||||
|
@ -8,7 +8,7 @@ import java.awt.Desktop
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
public val Page.Companion.threeJsHeader: HtmlFragment get() = scriptHeader("js/visionforge-three.js")
|
||||
public val VisionPage.Companion.threeJsHeader: HtmlFragment get() = scriptHeader("js/visionforge-three.js")
|
||||
|
||||
|
||||
@DFExperimental
|
||||
@ -19,10 +19,10 @@ public fun makeThreeJsFile(
|
||||
show: Boolean = true,
|
||||
content: HtmlVisionFragment,
|
||||
): Unit {
|
||||
val actualPath = Page(Global, content = content).makeFile(path) { actualPath ->
|
||||
val actualPath = VisionPage(Global, content = content).makeFile(path) { actualPath ->
|
||||
mapOf(
|
||||
"title" to Page.title(title),
|
||||
"threeJs" to Page.importScriptHeader("js/visionforge-three.js", resourceLocation, actualPath)
|
||||
"title" to VisionPage.title(title),
|
||||
"threeJs" to VisionPage.importScriptHeader("js/visionforge-three.js", resourceLocation, actualPath)
|
||||
)
|
||||
}
|
||||
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
|
Loading…
Reference in New Issue
Block a user