Refactor pages

This commit is contained in:
Alexander Nozik 2022-11-17 21:49:14 +03:00
parent 960d17855b
commit 3c51060e2e
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
32 changed files with 252 additions and 98 deletions

View File

@ -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.

View File

@ -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()
}
@ -38,9 +52,4 @@ apiValidation {
ignoredPackages.add("info.laht.threekt")
}
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"
//}
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")

View File

@ -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)

View File

@ -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"

View File

@ -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"
}
}
}

View File

@ -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())

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.*

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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())
}

View File

@ -0,0 +1,5 @@
package space.kscience.visionforge.markup
import space.kscience.visionforge.VisionPlugin
public expect class MarkupPlugin: VisionPlugin

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -3,6 +3,7 @@ plugins {
}
kotlin{
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Disabled
js{
binaries.library()
}

View File

@ -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)

View File

@ -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())