From 23a736a0129bbe737d795d30c4f81520695608e4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 20 Jun 2022 17:10:37 +0300 Subject: [PATCH] Refactor SPC home page to use decoupled representation from KTor --- src/main/kotlin/ru/mipt/spc/spcCollection.kt | 44 ++++++------- src/main/kotlin/ru/mipt/spc/spcHome.kt | 34 ++++------ .../space/kscience/snark/PageContext.kt | 15 ++++- .../space/kscience/snark/RouteBuilder.kt | 61 ------------------ .../kotlin/space/kscience/snark/SnarkRoute.kt | 64 +++++++++++++++++++ src/main/kotlin/space/kscience/snark/site.kt | 61 +++++++----------- 6 files changed, 134 insertions(+), 145 deletions(-) delete mode 100644 src/main/kotlin/space/kscience/snark/RouteBuilder.kt create mode 100644 src/main/kotlin/space/kscience/snark/SnarkRoute.kt diff --git a/src/main/kotlin/ru/mipt/spc/spcCollection.kt b/src/main/kotlin/ru/mipt/spc/spcCollection.kt index 3d7763e..168e01c 100644 --- a/src/main/kotlin/ru/mipt/spc/spcCollection.kt +++ b/src/main/kotlin/ru/mipt/spc/spcCollection.kt @@ -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) } } } diff --git a/src/main/kotlin/ru/mipt/spc/spcHome.kt b/src/main/kotlin/ru/mipt/spc/spcHome.kt index 7aa103c..4dadaa1 100644 --- a/src/main/kotlin/ru/mipt/spc/spcHome.kt +++ b/src/main/kotlin/ru/mipt/spc/spcHome.kt @@ -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" } diff --git a/src/main/kotlin/space/kscience/snark/PageContext.kt b/src/main/kotlin/space/kscience/snark/PageContext.kt index 777d02e..14cdb6d 100644 --- a/src/main/kotlin/space/kscience/snark/PageContext.kt +++ b/src/main/kotlin/space/kscience/snark/PageContext.kt @@ -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 = readDirectory(path) - return PageContext(rootUrl, parsedData) + return PageContext(context, rootUrl, parsedData) } /** diff --git a/src/main/kotlin/space/kscience/snark/RouteBuilder.kt b/src/main/kotlin/space/kscience/snark/RouteBuilder.kt deleted file mode 100644 index cb7df45..0000000 --- a/src/main/kotlin/space/kscience/snark/RouteBuilder.kt +++ /dev/null @@ -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)) - } -} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/SnarkRoute.kt b/src/main/kotlin/space/kscience/snark/SnarkRoute.kt new file mode 100644 index 0000000..6998c88 --- /dev/null +++ b/src/main/kotlin/space/kscience/snark/SnarkRoute.kt @@ -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)) +} \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/site.kt b/src/main/kotlin/space/kscience/snark/site.kt index eab9280..72b585d 100644 --- a/src/main/kotlin/space/kscience/snark/site.kt +++ b/src/main/kotlin/space/kscience/snark/site.kt @@ -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(typeOf()) { - - private val pageBuilders = HashMap) -> 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.generate(data: DataSet, meta: Meta) { - pageBuilders.forEach { (name, builder) -> - data(name, builder(data)) - } - } - -} \ No newline at end of file +//class SiteBuilderAction : AbstractAction(typeOf()) { +// +// private val pageBuilders = HashMap) -> 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.generate(data: DataSet, meta: Meta) { +// pageBuilders.forEach { (name, builder) -> +// data(name, builder(data)) +// } +// } +// +//} \ No newline at end of file