Refactor SPC home page to use decoupled representation from KTor

This commit is contained in:
Alexander Nozik 2022-06-20 17:10:37 +03:00
parent a907c57134
commit 23a736a012
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
6 changed files with 134 additions and 145 deletions

View File

@ -1,10 +1,6 @@
package ru.mipt.spc
import html5up.forty.fortyScripts
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.html.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
@ -15,6 +11,13 @@ import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.string
import space.kscience.snark.*
import kotlin.collections.Map
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.forEach
import kotlin.collections.joinToString
import kotlin.collections.set
import kotlin.collections.sortedBy
context(PageContext) private fun FlowContent.spcSpotlightContent(
landing: HtmlData,
@ -89,7 +92,7 @@ context(PageContext) private fun FlowContent.spcSpotlightContent(
}
context(PageContext) internal fun Route.spcSpotlight(
context(PageContext) internal fun SnarkRoute.spcSpotlight(
name: String,
contentFilter: (Name, Meta) -> Boolean,
) {
@ -97,29 +100,22 @@ context(PageContext) internal fun Route.spcSpotlight(
val content = resolveAllHtml(contentFilter)
val meta = body.meta
get(name) {
withRequest(call.request) {
call.respondHtml {
val title = meta["title"].string ?: SPC_TITLE
spcHead(title)
body("is-preload") {
wrapper {
spcSpotlightContent(body, content)
}
fortyScripts()
}
page(name) {
val title = meta["title"].string ?: SPC_TITLE
spcHead(title)
body("is-preload") {
wrapper {
spcSpotlightContent(body, content)
}
fortyScripts()
}
}
content.forEach { (name, contentBody) ->
get(name.tokens.joinToString("/")) {
withRequest(call.request) {
call.respondHtml {
spcPageContent(contentBody.meta) {
htmlData(contentBody)
}
}
page(name.tokens.joinToString("/")){
spcPageContent(contentBody.meta) {
htmlData(contentBody)
}
}
}

View File

@ -2,13 +2,13 @@ package ru.mipt.spc
import html5up.forty.fortyScripts
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.log
import io.ktor.server.html.respondHtml
import io.ktor.server.routing.*
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.data.filterByType
import space.kscience.dataforge.data.forEach
import space.kscience.dataforge.meta.Meta
@ -58,17 +58,13 @@ context(PageContext) internal fun HTML.spcPageContent(
}
context(PageContext) internal fun Route.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) {
get(subRoute) {
withRequest(call.request) {
call.respondHtml {
spcPageContent(meta, fragment = fragment)
}
}
context(PageContext) internal fun SnarkRoute.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) {
page(subRoute) {
spcPageContent(meta, fragment = fragment)
}
}
context(PageContext) internal fun Route.spcPage(
context(PageContext) internal fun SnarkRoute.spcPage(
subRoute: String,
dataPath: Name = subRoute.replace("/", ".").parseAsName(),
more: FlowContent.() -> Unit = {},
@ -80,14 +76,14 @@ context(PageContext) internal fun Route.spcPage(
more()
}
} else {
application.log.error("Content for page with path $dataPath not found")
logger.error { "Content for page with path $dataPath not found" }
}
}
/**
* Route a directory
*/
context(PageContext) internal fun Route.spcDirectory(
context(PageContext) internal fun SnarkRoute.spcDirectory(
subRoute: String,
dataPath: Name = subRoute.replace("/", ".").parseAsName(),
) {
@ -104,14 +100,14 @@ context(PageContext) internal fun Route.spcDirectory(
}
}
context(PageContext) internal fun Route.spcPage(
context(PageContext) internal fun SnarkRoute.spcPage(
name: Name,
more: FlowContent.() -> Unit = {},
) {
spcPage(name.tokens.joinToString("/"), name, more)
}
context(PageContext) private fun HTML.spcHome() {
context(PageContext, HTML) private fun HTML.spcHome() {
spcHead()
body("is-preload") {
wrapper {
@ -309,13 +305,11 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin
snark(homePageContext) {
staticDirectory("assets", rootPath.resolve("assets"))
staticDirectory("images", rootPath.resolve("images"))
page { spcHome() }
}
with(homePageContext) {
page { spcHome() }
spcDirectory("consulting")
spcPage("ru/consulting")
spcDirectory("ru/consulting")
spcSpotlight("team") { _, m -> m["type"].string == "team" }
spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" }

View File

@ -6,6 +6,8 @@ import io.ktor.server.plugins.origin
import io.ktor.server.request.ApplicationRequest
import io.ktor.server.request.host
import io.ktor.server.request.port
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
@ -16,7 +18,13 @@ import space.kscience.dataforge.names.startsWith
import space.kscience.snark.PageContext.Companion.INDEX_PAGE_NAME
import java.nio.file.Path
data class PageContext(val path: String, val pageMeta: Meta, val data: DataSet<*>) {
data class PageContext(
override val context: Context,
val path: String,
val pageMeta: Meta,
val data: DataSet<*>,
) : ContextAware {
val language: String? by pageMeta.string()
companion object {
@ -61,12 +69,13 @@ fun PageContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = r
internal val Data<*>.published: Boolean get() = meta["published"].string != "false"
fun PageContext(rootUrl: String, data: DataSet<*>): PageContext = PageContext(rootUrl, data.meta, data)
fun PageContext(dfContext: Context, rootUrl: String, data: DataSet<*>): PageContext =
PageContext(dfContext, rootUrl, data.meta, data)
fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): PageContext {
val parsedData: DataSet<Any> = readDirectory(path)
return PageContext(rootUrl, parsedData)
return PageContext(context, rootUrl, parsedData)
}
/**

View File

@ -1,61 +0,0 @@
package space.kscience.snark
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.file
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.html.HTML
import java.nio.file.Path
interface RouteBuilder {
val pageContext: PageContext
fun staticFile(remotePath: String, file: Path)
fun staticDirectory(remotePath: String, directory: Path)
fun page(route: String = "", content: context(PageContext) HTML.() -> Unit)
fun route(route: String, block: RouteBuilder.() -> Unit)
}
class KtorRouteBuilder(override val pageContext: PageContext, private val ktorRoute: Route) : RouteBuilder {
override fun staticFile(remotePath: String, file: Path) {
ktorRoute.file(remotePath, file.toFile())
}
override fun staticDirectory(remotePath: String, directory: Path) {
ktorRoute.static(remotePath) {
files(directory.toFile())
}
}
override fun page(route: String, content: context(PageContext) HTML.() -> Unit) {
ktorRoute.get(route) {
with(pageContext) {
withRequest(call.request) {
val innerContext = this
call.respondHtml {
content(innerContext, this)
}
}
}
}
}
override fun route(route: String, block: RouteBuilder.() -> Unit) {
ktorRoute.route(route) {
block(KtorRouteBuilder(pageContext, this))
}
}
}
fun Route.snark(pageContext: PageContext, block: context(PageContext) RouteBuilder.() -> Unit) {
with(pageContext){
block(KtorRouteBuilder(pageContext, this@snark))
}
}

View File

@ -0,0 +1,64 @@
package space.kscience.snark
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.file
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.html.HTML
import java.nio.file.Path
/**
* An abstraction, which is used to render sites to the different rendering engines
*/
interface SnarkRoute {
fun staticFile(remotePath: String, file: Path)
fun staticDirectory(remotePath: String, directory: Path)
context(PageContext) fun page(route: String = "", content: context(PageContext, HTML) () -> Unit)
context(PageContext) fun route(route: String, block: context(PageContext, SnarkRoute) () -> Unit)
}
class KtorRouteBuilder(private val ktorRoute: Route) : SnarkRoute {
override fun staticFile(remotePath: String, file: Path) {
ktorRoute.file(remotePath, file.toFile())
}
override fun staticDirectory(remotePath: String, directory: Path) {
ktorRoute.static(remotePath) {
files(directory.toFile())
}
}
context(PageContext) override fun page(route: String, content: context(PageContext, HTML)() -> Unit) {
ktorRoute.get(route) {
withRequest(call.request) {
call.respondHtml {
content(this@PageContext, this)
}
}
}
}
context(PageContext) override fun route(
route: String,
block: context(PageContext, SnarkRoute)() -> Unit,
) {
ktorRoute.route(route) {
block(this@PageContext, KtorRouteBuilder(this))
}
}
}
inline fun Route.snark(
pageContext: PageContext,
block: context(PageContext, SnarkRoute)() -> Unit,
) {
block(pageContext, KtorRouteBuilder(this@snark))
}

View File

@ -1,39 +1,26 @@
package ru.mipt.spc
import kotlinx.html.TagConsumer
import space.kscience.dataforge.actions.AbstractAction
import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.DataSet
import space.kscience.dataforge.data.DataSetBuilder
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.copy
import space.kscience.dataforge.names.Name
import space.kscience.snark.HtmlData
import space.kscience.snark.HtmlFragment
import space.kscience.snark.PageContext
import kotlin.reflect.typeOf
class SiteBuilderAction : AbstractAction<Any, HtmlFragment>(typeOf<HtmlFragment>()) {
private val pageBuilders = HashMap<Name, (DataSet<*>) -> HtmlData>()
fun page(name: Name, meta: Meta = Meta.EMPTY, builder: context(PageContext) TagConsumer<*>.() -> Unit) {
val prefix = name.tokens.joinToString(separator = "/", prefix = "/")
pageBuilders[name] = { dataset ->
val fragment: HtmlFragment = {
builder.invoke(PageContext(prefix, dataset), this)
}
Data(fragment, meta.copy {
"name" put name.toString()
})
}
}
override fun DataSetBuilder<HtmlFragment>.generate(data: DataSet<Any>, meta: Meta) {
pageBuilders.forEach { (name, builder) ->
data(name, builder(data))
}
}
}
//class SiteBuilderAction : AbstractAction<Any, HtmlFragment>(typeOf<HtmlFragment>()) {
//
// private val pageBuilders = HashMap<Name, (DataSet<*>) -> HtmlData>()
//
// fun page(name: Name, meta: Meta = Meta.EMPTY, builder: context(PageContext) TagConsumer<*>.() -> Unit) {
// val prefix = name.tokens.joinToString(separator = "/", prefix = "/")
// pageBuilders[name] = { dataset ->
// val fragment: HtmlFragment = {
// builder.invoke(PageContext(prefix, dataset), this)
// }
// Data(fragment, meta.copy {
// "name" put name.toString()
// })
// }
// }
//
//
// override fun DataSetBuilder<HtmlFragment>.generate(data: DataSet<Any>, meta: Meta) {
// pageBuilders.forEach { (name, builder) ->
// data(name, builder(data))
// }
// }
//
//}