Skip to content

Commit

Permalink
Merge pull request #19 from InkApplications/static-html
Browse files Browse the repository at this point in the history
Add Static Html Script renderer
  • Loading branch information
ReneeVandervelde authored Aug 18, 2024
2 parents f4579ef + c4b8556 commit d57f0a2
Show file tree
Hide file tree
Showing 21 changed files with 507 additions and 1 deletion.
23 changes: 23 additions & 0 deletions .github/workflows/pushes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,26 @@ on:
jobs:
tests:
uses: inkapplications/.github/.github/workflows/[email protected]
publish-site:
name: Publish Site
needs: [tests]
runs-on: ubuntu-latest
steps:
-
name: Build Static Sample
run: ./gradlew sample-web:buildStatic sample-web:assemble
-
name: Create Deploy Directory
run: |
mkdir -p build/pages
cp -r sample-web/build/static/* build/pages/
cp -r sample-web/build/dist/js/productionExecutable/* build/pages/
mv build/pages/sample.html build/pages/index.html
-
name: Upload Pages Artifact
uses: actions/[email protected]
with:
path: build/pages
-
name: Deploy to GitHub Pages
uses: actions/[email protected]
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
tests:
name: Test
uses: inkapplications/.github/.github/workflows/[email protected]
publish:
publish-maven:
name: Publish to Maven Central
needs: [tests]
secrets: inherit
Expand Down
11 changes: 11 additions & 0 deletions bin/render-static-sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

$SCRIPTDIR/render-ui $SCRIPTDIR/../sample-web/src/staticMain/Sample.inkui.kts
11 changes: 11 additions & 0 deletions bin/render-ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

$SCRIPTDIR/../gradlew render-static-html:installDist --quiet --build-file $SCRIPTDIR/../build.gradle.kts && $SCRIPTDIR/../render-static-html/build/install/render-ui/bin/render-ui "$@"
16 changes: 16 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ module = "org.jetbrains.kotlinx:kotlinx-coroutines-core"
version.ref = "kotlin"
module = "org.jetbrains.kotlin:kotlin-test-junit"

[libraries.kotlin-scripting-common]
version.ref = "kotlin"
module = "org.jetbrains.kotlin:kotlin-scripting-common"

[libraries.kotlin-scripting-jvm-core]
version.ref = "kotlin"
module = "org.jetbrains.kotlin:kotlin-scripting-jvm"

[libraries.kotlin-scripting-jvm-host]
version.ref = "kotlin"
module = "org.jetbrains.kotlin:kotlin-scripting-jvm-host"

[libraries.kotlinx-html]
version = "0.11.0"
module = "org.jetbrains.kotlinx:kotlinx-html-jvm"

[libraries.android-gradle]
version = "8.2.0"
module = "com.android.tools.build:gradle"
Expand Down
18 changes: 18 additions & 0 deletions render-static-html/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
application
kotlin("jvm")
}

application {
applicationName = "render-ui"
mainClass.set("ink.ui.render.statichtml.MainKt")
}

dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlin.scripting.common)
implementation(libs.kotlin.scripting.jvm.core)
implementation(libs.kotlin.scripting.jvm.host)
implementation(libs.kotlinx.html)
api(projects.structures)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ink.ui.render.statichtml

import ink.ui.render.statichtml.renderer.*
import ink.ui.render.statichtml.renderer.CompositeElementRenderer
import ink.ui.render.statichtml.renderer.TextRenderer
import ink.ui.structures.elements.UiElement
import ink.ui.structures.layouts.*
import kotlinx.html.*
import kotlinx.html.dom.createHTMLDocument
import kotlinx.html.dom.serialize

class HtmlRenderer {
private val builtInRenderers = listOf(
ListRenderer,
TextRenderer,
StatusRenderer(null)
)
private val renderer = CompositeElementRenderer(builtInRenderers)

fun renderElement(element: UiElement): TagConsumer<*>.() -> Unit = {
renderWith(
element = element,
consumer = this,
renderer = renderer,
)
}

fun renderLayout(uiLayout: UiLayout): TagConsumer<*>.() -> Unit = {
when (uiLayout) {
is CenteredElementLayout -> TODO()
is FixedGridLayout -> TODO()
is PageLayout -> section {
with(renderer) {
render(uiLayout.body, renderer)
}
}

is ScrollingListLayout -> TODO()
}
}

fun renderDocument(
pageTitle: String,
pageHeaders: List<TagConsumer<*>.() -> Unit>,
bodies: List<TagConsumer<*>.() -> Unit>,
stylesheets: List<String>,
sectioned: Boolean = false,
contentBreak: Boolean = false
): String {
return createHTMLDocument().html {
attributes["lang"] = "en"
head {
stylesheets.forEach {
styleLink(it)
}
title { +pageTitle }
}
body(classes = when {
sectioned -> "sectioned"
contentBreak -> "content-break"
else -> ""
}) {
if (pageHeaders.isNotEmpty()) {
header("content-break".takeIf { sectioned }) {
pageHeaders.forEach { it(consumer) }
}
}
bodies.forEach { it(consumer) }
}
}.serialize(prettyPrint = true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ink.ui.render.statichtml

import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.defaultImports
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm

object InkUiConfig: ScriptCompilationConfiguration({
jvm {
dependenciesFromCurrentContext(wholeClasspath = true)
defaultImports("kotlinx.html.*")
defaultImports("ink.ui.structures.*")
defaultImports("ink.ui.structures.layouts.*")
defaultImports("ink.ui.structures.elements.*")
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ink.ui.render.statichtml

import ink.ui.structures.elements.UiElement
import ink.ui.structures.layouts.*
import kotlinx.html.*
import kotlinx.html.dom.createHTMLDocument
import java.io.File
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.util.isError
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate

@KotlinScript(
fileExtension = "inkui.kts",
compilationConfiguration = InkUiConfig::class
)
@Suppress("unused")
abstract class InkUiScript {
private var pageHeaders: MutableList<TagConsumer<*>.() -> Unit> = mutableListOf()
private var bodies: MutableList<TagConsumer<*>.() -> Unit> = mutableListOf()
private var styles: MutableList<String> = mutableListOf()
private val document = createHTMLDocument()
private val renderer = HtmlRenderer()
internal lateinit var fileName: String
var title: String? = null
var sectioned: Boolean = false
var contentBreak: Boolean = false

fun addPageHeader(element: UiElement) {
pageHeaders.add(renderer.renderElement(element))
}

fun addPageHeader(block: TagConsumer<*>.() -> Unit) {
pageHeaders.add(block)
}

fun addBody(block: TagConsumer<*>.() -> Unit) {
bodies.add(block)
}

fun addBody(layout: UiLayout) {
bodies.add(renderer.renderLayout(layout))
}

fun addStyle(stylesheet: String) {
styles += stylesheet
}

fun useHostedStyles() {
styles += "https://assets.inkapplications.com/css/main-v1.3.css"
}

internal fun getHtml(): String {
return renderer.renderDocument(
pageTitle = title ?: fileName,
pageHeaders = pageHeaders,
bodies = bodies,
stylesheets = styles,
sectioned = sectioned,
contentBreak = contentBreak,
)
}


companion object {
internal fun evalFile(scriptFile: File): ResultWithDiagnostics<EvaluationResult> {
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<InkUiScript>()
return BasicJvmScriptingHost()
.eval(scriptFile.toScriptSource(), compilationConfiguration, null)
.apply {
if (!isError()) {
val script = (valueOrThrow().returnValue.scriptInstance as InkUiScript)
script.fileName = scriptFile.name.replace(Regex("\\.inkui\\.kts$", RegexOption.IGNORE_CASE), "")
}
}
}
}
}
33 changes: 33 additions & 0 deletions render-static-html/src/main/java/ink/ui/render/statichtml/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ink.ui.render.statichtml

import java.io.File
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.onFailure
import kotlin.script.experimental.api.valueOrThrow
import kotlin.system.exitProcess

fun main(args: Array<String>) {
val file = args.firstOrNull()
?.let(::File)
?: run {
println("Usage: render-ui <file>")
exitProcess(1)
}
InkUiScript.evalFile(file)
.onFailure { result ->
result.reports
.filter { it.severity > ScriptDiagnostic.Severity.INFO }
.forEach {
println(it.message)
it.exception?.printStackTrace()
}
exitProcess(1)
}
.valueOrThrow()
.returnValue
.scriptInstance
.let { it as InkUiScript }
.run {
print(getHtml())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ink.ui.render.statichtml.renderer

import ink.ui.structures.elements.UiElement
import kotlinx.html.TagConsumer

internal class CompositeElementRenderer(
private val renderers: List<ElementRenderer> = emptyList(),
): ElementRenderer {
override fun TagConsumer<*>.render(element: UiElement, parent: ElementRenderer): RenderResult {
renderers.forEach { renderer ->
val result = renderWith(
element = element,
consumer = this@render,
renderer = renderer,
parent = this@CompositeElementRenderer,
)
if (result == RenderResult.Rendered) {
return RenderResult.Rendered
}
}
return RenderResult.NotRendered
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ink.ui.render.statichtml.renderer

import ink.ui.structures.elements.UiElement
import kotlinx.html.TagConsumer

interface ElementRenderer {
fun TagConsumer<*>.render(element: UiElement, parent: ElementRenderer): RenderResult
}

inline fun <reified T> renderer(crossinline render: TagConsumer<*>.(T) -> Unit) = object: ElementRenderer{
override fun TagConsumer<*>.render(element: UiElement, parent: ElementRenderer): RenderResult {
if (element is T) {
render(element)
return RenderResult.Rendered
}
return RenderResult.NotRendered
}
}

fun renderWith(
element: UiElement,
consumer: TagConsumer<*>,
renderer: ElementRenderer,
parent: ElementRenderer = renderer,
): RenderResult {
return with(renderer) {
with(consumer) {
render(element, parent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ink.ui.render.statichtml.renderer

import ink.ui.structures.GroupingStyle
import ink.ui.structures.Positioning
import ink.ui.structures.elements.ElementList
import ink.ui.structures.elements.UiElement
import kotlinx.html.*

object ListRenderer: ElementRenderer {
override fun TagConsumer<*>.render(element: UiElement, parent: ElementRenderer): RenderResult {
if (element !is ElementList) return RenderResult.NotRendered

div(
classes = when (element.groupingStyle) {
GroupingStyle.Unified -> "unified-list"
GroupingStyle.Items -> "item-list"
GroupingStyle.Sections -> "section-list"
}
) {
attributes["style"] = when (element.positioning) {
Positioning.Start -> "justify-content: start"
Positioning.Center -> "justify-content: center"
}
element.items.forEach { item ->
div {
renderWith(item, consumer, parent)
}
}
}

return RenderResult.Rendered
}
}


Loading

0 comments on commit d57f0a2

Please sign in to comment.