Refactor SPC home page to use decoupled representation from KTor
This commit is contained in:
parent
a907c57134
commit
23a736a012
@ -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,9 +100,7 @@ context(PageContext) internal fun Route.spcSpotlight(
|
||||
val content = resolveAllHtml(contentFilter)
|
||||
|
||||
val meta = body.meta
|
||||
get(name) {
|
||||
withRequest(call.request) {
|
||||
call.respondHtml {
|
||||
page(name) {
|
||||
val title = meta["title"].string ?: SPC_TITLE
|
||||
spcHead(title)
|
||||
body("is-preload") {
|
||||
@ -110,17 +111,12 @@ context(PageContext) internal fun Route.spcSpotlight(
|
||||
fortyScripts()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content.forEach { (name, contentBody) ->
|
||||
get(name.tokens.joinToString("/")) {
|
||||
withRequest(call.request) {
|
||||
call.respondHtml {
|
||||
page(name.tokens.joinToString("/")){
|
||||
spcPageContent(contentBody.meta) {
|
||||
htmlData(contentBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
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" }
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
64
src/main/kotlin/space/kscience/snark/SnarkRoute.kt
Normal file
64
src/main/kotlin/space/kscience/snark/SnarkRoute.kt
Normal 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))
|
||||
}
|
@ -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))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
Loading…
Reference in New Issue
Block a user