1
0
forked from SPC/spc-site

Refactor page placement.

This commit is contained in:
Alexander Nozik 2022-06-22 12:18:35 +03:00
parent 600a9b5529
commit 4966bfc9b3
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
11 changed files with 189 additions and 156 deletions

View File

@ -1,7 +1,7 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.SiteContext import space.kscience.snark.SiteData
import space.kscience.snark.resolveRef import space.kscience.snark.resolveRef
@ -201,7 +201,7 @@ internal fun FlowContent.fortyFooter() {
} }
} }
context(SiteContext) internal fun BODY.fortyScripts() { context(SiteData) internal fun BODY.fortyScripts() {
script { script {
src = resolveRef("assets/js/jquery.min.js") src = resolveRef("assets/js/jquery.min.js")
} }

View File

@ -1,9 +1,9 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.SiteContext import space.kscience.snark.SiteData
context(SiteContext) internal fun HTML.landing(){ context(SiteData) internal fun HTML.landing(){
head { head {
title { title {
} }

View File

@ -1,10 +1,10 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.SiteContext import space.kscience.snark.SiteData
import space.kscience.snark.resolveRef import space.kscience.snark.resolveRef
context(SiteContext) internal fun HTML.fortyPage(){ context(SiteData) internal fun HTML.fortyPage(){
head { head {
title { title {
} }

View File

@ -80,7 +80,7 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program"
private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses" private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses"
private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners" private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners"
context(SiteContext) private fun FlowContent.programSection() { context(SiteData) private fun FlowContent.programSection() {
val programBlock = resolveHtml(PROGRAM_PATH)!! val programBlock = resolveHtml(PROGRAM_PATH)!!
val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!! val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!!
div("inner") { div("inner") {
@ -97,7 +97,7 @@ context(SiteContext) private fun FlowContent.programSection() {
} }
} }
context(SiteContext) private fun FlowContent.partners() { context(SiteData) private fun FlowContent.partners() {
//val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY //val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY
div("inner") { div("inner") {
@ -127,7 +127,7 @@ context(SiteContext) private fun FlowContent.partners() {
// val photo: String? by meta.string() // val photo: String? by meta.string()
//} //}
context(SiteContext) private fun FlowContent.team() { context(SiteData) private fun FlowContent.team() {
val team = findByType("magprog_team").values.sortedBy { it.order } val team = findByType("magprog_team").values.sortedBy { it.order }
div("inner") { div("inner") {
@ -182,7 +182,7 @@ context(SiteContext) private fun FlowContent.team() {
// } // }
} }
context(SiteContext) private fun FlowContent.mentors() { context(SiteData) private fun FlowContent.mentors() {
val mentors = findByType("magprog_mentor").entries.sortedBy { it.value.id } val mentors = findByType("magprog_mentor").entries.sortedBy { it.value.id }
div("inner") { div("inner") {
@ -219,12 +219,12 @@ context(SiteContext) private fun FlowContent.mentors() {
} }
} }
context(SiteContext) internal fun FlowContent.contacts() { context(SiteData) internal fun FlowContent.contacts() {
} }
context(SiteContext) internal fun HTML.magProgHead(title: String) { context(SiteData) internal fun HTML.magProgHead(title: String) {
head { head {
this.title = title this.title = title
meta { meta {
@ -251,7 +251,7 @@ context(SiteContext) internal fun HTML.magProgHead(title: String) {
} }
} }
context(SiteContext) internal fun BODY.magProgFooter() { context(SiteData) internal fun BODY.magProgFooter() {
footer("wrapper style1-alt") { footer("wrapper style1-alt") {
id = "footer" id = "footer"
div("inner") { div("inner") {
@ -296,7 +296,7 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
val snark = context.fetch(SnarkPlugin) val snark = context.fetch(SnarkPlugin)
val magProgSiteContext: SiteContext = snark.read(dataPath.resolve("content"), prefix) val magProgSiteContext: SiteData = snark.readDirectory(dataPath.resolve("content"), prefix)
routing { routing {
route(prefix) { route(prefix) {

View File

@ -15,7 +15,7 @@ import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
context(SiteContext) private fun FlowContent.spcSpotlightContent( context(SiteData) private fun FlowContent.spcSpotlightContent(
landing: HtmlData, landing: HtmlData,
content: Map<Name, HtmlData>, content: Map<Name, HtmlData>,
) { ) {
@ -88,11 +88,12 @@ context(SiteContext) private fun FlowContent.spcSpotlightContent(
} }
context(SiteContext) internal fun SiteBuilder.spcSpotlight( context(SiteData) internal fun SiteBuilder.spcSpotlight(
name: String, address: String,
contentFilter: (Name, Meta) -> Boolean, contentFilter: (Name, Meta) -> Boolean,
) { ) {
val body = resolveHtml(name.parseAsName()) ?: error("Could not find body for $name") val name = address.parseAsName()
val body = resolveHtml(name) ?: error("Could not find body for $name")
val content = resolveAllHtml(contentFilter) val content = resolveAllHtml(contentFilter)
val meta = body.meta val meta = body.meta
@ -109,7 +110,7 @@ context(SiteContext) internal fun SiteBuilder.spcSpotlight(
} }
content.forEach { (name, contentBody) -> content.forEach { (name, contentBody) ->
page(name.tokens.joinToString("/")){ page(name){
spcPageContent(contentBody.meta) { spcPageContent(contentBody.meta) {
htmlData(contentBody) htmlData(contentBody)
} }

View File

@ -9,18 +9,20 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.error import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.data.filterByType import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.forEach
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.string import space.kscience.dataforge.values.string
import space.kscience.snark.* import space.kscience.snark.*
import java.nio.file.Path import java.nio.file.Path
import kotlin.reflect.typeOf
context(SiteContext) internal fun HTML.spcPageContent( context(SiteData) internal fun HTML.spcPageContent(
meta: Meta, meta: Meta,
title: String = meta["title"].string ?: SPC_TITLE, title: String = meta["title"].string ?: SPC_TITLE,
fragment: FlowContent.() -> Unit, fragment: FlowContent.() -> Unit,
@ -58,18 +60,18 @@ context(SiteContext) internal fun HTML.spcPageContent(
} }
context(SiteContext) internal fun SiteBuilder.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) { internal fun SiteBuilder.spcPage(subRoute: Name, meta: Meta, fragment: FlowContent.() -> Unit) {
page(subRoute) { page(subRoute) {
spcPageContent(meta, fragment = fragment) spcPageContent(meta, fragment = fragment)
} }
} }
context(SiteContext) internal fun SiteBuilder.spcPage( internal fun SiteBuilder.spcPage(
subRoute: String, subRoute: Name,
dataPath: Name = subRoute.replace("/", ".").parseAsName(), dataPath: Name = subRoute,
more: FlowContent.() -> Unit = {}, more: FlowContent.() -> Unit = {},
) { ) {
val data = resolveHtml(dataPath) val data = data.resolveHtml(dataPath)
if (data != null) { if (data != null) {
spcPage(subRoute, data.meta) { spcPage(subRoute, data.meta) {
htmlData(data) htmlData(data)
@ -80,34 +82,46 @@ context(SiteContext) internal fun SiteBuilder.spcPage(
} }
} }
/** @Suppress("UNCHECKED_CAST")
* Route a directory internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data ->
*/ if(data.type == typeOf<HtmlFragment>()) {
context(SiteContext) internal fun SiteBuilder.spcDirectory( data as Data<HtmlFragment>
subRoute: String, page {
dataPath: Name = subRoute.replace("/", ".").parseAsName(), spcPageContent(data.meta) {
) { htmlData(data)
data.filterByType<HtmlFragment> { name, _ -> name.startsWith(dataPath) }.forEach { html -> }
val pageName = if (html.name.lastOrNull()?.body == SiteContext.INDEX_PAGE_NAME) {
html.name.cutLast()
} else {
html.name
}
spcPage(pageName.tokens.joinToString(separator = "/"), html.meta) {
htmlData(html)
} }
} }
} }
context(SiteContext) internal fun SiteBuilder.spcPage( ///**
// * Route a directory
// */
//internal fun SiteBuilder.spcDirectory(
// subRoute: String,
// dataPath: Name = subRoute.replace("/", ".").parseAsName(),
//) {
// data.filterByType<HtmlFragment> { name, _ -> name.startsWith(dataPath) }.forEach { html ->
// val pageName = if (html.name.lastOrNull()?.body == SiteData.INDEX_PAGE_NAME) {
// html.name.cutLast()
// } else {
// html.name
// }
//
// spcPage(pageName.tokens.joinToString(separator = "/"), html.meta) {
// htmlData(html)
// }
// }
//}
internal fun SiteBuilder.spcPage(
name: Name, name: Name,
more: FlowContent.() -> Unit = {}, more: FlowContent.() -> Unit = {},
) { ) {
spcPage(name.tokens.joinToString("/"), name, more) spcPage(name, name, more)
} }
context(SiteContext, HTML) private fun HTML.spcHome() { context(SiteData, HTML) private fun HTML.spcHome() {
spcHead() spcHead()
body("is-preload") { body("is-preload") {
wrapper { wrapper {
@ -298,18 +312,18 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin
val snark = context.fetch(SnarkPlugin) val snark = context.fetch(SnarkPlugin)
val homePageContext = snark.read(rootPath.resolve("content"), prefix) val homePageContext = snark.readDirectory(rootPath.resolve("content"), prefix)
routing { routing {
route(prefix) { route(prefix) {
snarkSite(homePageContext) { snarkSite(homePageContext) {
staticDirectory("assets", rootPath.resolve("assets")) assetDirectory("assets", rootPath.resolve("assets"))
staticDirectory("images", rootPath.resolve("images")) assetDirectory("images", rootPath.resolve("images"))
page { spcHome() } page { spcHome() }
spcDirectory("consulting") pages("consulting", dataRenderer = FortyDataRenderer)
spcDirectory("ru/consulting") //pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer)
spcSpotlight("team") { _, m -> m["type"].string == "team" } spcSpotlight("team") { _, m -> m["type"].string == "team" }
spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" } spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" }

View File

@ -1,14 +1,14 @@
package ru.mipt.spc package ru.mipt.spc
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.SiteContext import space.kscience.snark.SiteData
import space.kscience.snark.homeRef import space.kscience.snark.homeRef
import space.kscience.snark.resolveRef import space.kscience.snark.resolveRef
internal const val SPC_TITLE = "Scientific Programming Centre" internal const val SPC_TITLE = "Scientific Programming Centre"
context(SiteContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { context(SiteData) internal fun HTML.spcHead(title: String = SPC_TITLE) {
head { head {
title { title {
+title +title
@ -27,7 +27,7 @@ context(SiteContext) internal fun HTML.spcHead(title: String = SPC_TITLE) {
} }
} }
context(SiteContext) internal fun FlowContent.spcHomeMenu() { context(SiteData) internal fun FlowContent.spcHomeMenu() {
nav { nav {
id = "menu" id = "menu"
ul("links") { ul("links") {
@ -79,7 +79,7 @@ context(SiteContext) internal fun FlowContent.spcHomeMenu() {
} }
} }
context(SiteContext) internal fun FlowContent.spcFooter() { context(SiteData) internal fun FlowContent.spcFooter() {
footer { footer {
id = "footer" id = "footer"
div("inner") { div("inner") {
@ -129,7 +129,7 @@ context(SiteContext) internal fun FlowContent.spcFooter() {
} }
} }
context(SiteContext) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { context(SiteData) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
div { div {
id = "wrapper" id = "wrapper"
// Header // Header

View File

@ -9,71 +9,78 @@ import io.ktor.server.routing.get
import kotlinx.html.HTML import kotlinx.html.HTML
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import java.nio.file.Path import java.nio.file.Path
internal fun Name.toWebPath() = tokens.joinToString(separator = "/")
/** /**
* An abstraction, which is used to render sites to the different rendering engines * An abstraction, which is used to render sites to the different rendering engines
*/ */
interface SiteBuilder : ContextAware { interface SiteBuilder : ContextAware {
val siteContext: SiteContext val data: SiteData
override val context: Context get() = siteContext.context override val context: Context get() = data.context
fun staticFile(remotePath: String, file: Path) fun assetFile(remotePath: String, file: Path)
fun staticDirectory(remotePath: String, directory: Path) fun assetDirectory(remotePath: String, directory: Path)
fun staticResourceFile(remotePath: String, resourcesPath: String) fun assetResourceFile(remotePath: String, resourcesPath: String)
fun staticResourceDirectory(resourcesPath: String) fun assetResourceDirectory(resourcesPath: String)
fun page(route: String = "", content: context(SiteContext, HTML) () -> Unit) fun page(route: Name = Name.EMPTY, content: context(SiteData, HTML) () -> Unit)
/** /**
* Create a route * Create a route
*/ */
fun route(subRoute: String): SiteBuilder fun route(subRoute: Name): SiteBuilder
} }
public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit) { public inline fun SiteBuilder.route(route: Name, block: SiteBuilder.() -> Unit) {
route(route).apply(block) route(route).apply(block)
} }
class KtorSiteRoute(override val siteContext: SiteContext, private val ktorRoute: Route) : SiteBuilder { public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit) {
override fun staticFile(remotePath: String, file: Path) { route(route.parseAsName()).apply(block)
}
class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) : SiteBuilder {
override fun assetFile(remotePath: String, file: Path) {
ktorRoute.file(remotePath, file.toFile()) ktorRoute.file(remotePath, file.toFile())
} }
override fun staticDirectory(remotePath: String, directory: Path) { override fun assetDirectory(remotePath: String, directory: Path) {
ktorRoute.static(remotePath) { ktorRoute.static(remotePath) {
files(directory.toFile()) files(directory.toFile())
} }
} }
override fun page(route: String, content: context(SiteContext, HTML)() -> Unit) { override fun page(route: Name, content: context(SiteData, HTML)() -> Unit) {
ktorRoute.get(route) { ktorRoute.get(route.toWebPath()) {
call.respondHtml { call.respondHtml {
content(siteContext.copyWithRequestHost(call.request), this) content(data.copyWithRequestHost(call.request), this)
} }
} }
} }
override fun route(subRoute: String): SiteBuilder = override fun route(subRoute: Name): SiteBuilder =
KtorSiteRoute(siteContext, ktorRoute.createRouteFromPath(subRoute)) KtorSiteRoute(data, ktorRoute.createRouteFromPath(subRoute.toWebPath()))
override fun staticResourceFile(remotePath: String, resourcesPath: String) { override fun assetResourceFile(remotePath: String, resourcesPath: String) {
ktorRoute.resource(resourcesPath, resourcesPath) ktorRoute.resource(resourcesPath, resourcesPath)
} }
override fun staticResourceDirectory(resourcesPath: String) { override fun assetResourceDirectory(resourcesPath: String) {
ktorRoute.resources(resourcesPath) ktorRoute.resources(resourcesPath)
} }
} }
inline fun Route.snarkSite( inline fun Route.snarkSite(
siteContext: SiteContext, siteContext: SiteData,
block: context(SiteContext, SiteBuilder)() -> Unit, block: context(SiteData, SiteBuilder)() -> Unit,
) { ) {
block(siteContext, KtorSiteRoute(siteContext, this@snarkSite)) block(siteContext, KtorSiteRoute(siteContext, this@snarkSite))
} }

View File

@ -15,15 +15,15 @@ import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.snark.SiteContext.Companion.INDEX_PAGE_NAME import space.kscience.snark.SiteData.Companion.INDEX_PAGE_NAME
import java.nio.file.Path import java.nio.file.Path
data class SiteContext( data class SiteData(
val snark: SnarkPlugin, val snark: SnarkPlugin,
val path: String,
val meta: Meta,
val data: DataTree<*>, val data: DataTree<*>,
) : ContextAware { val urlPath: String,
override val meta: Meta = data.meta
) : ContextAware, DataTree<Any> by data {
override val context: Context get() = snark.context override val context: Context get() = snark.context
@ -37,15 +37,15 @@ data class SiteContext(
/** /**
* Resolve a resource full path by its name * Resolve a resource full path by its name
*/ */
fun SiteContext.resolveRef(name: String): String = "${path.removeSuffix("/")}/$name" fun SiteData.resolveRef(name: String): String = "${urlPath.removeSuffix("/")}/$name"
fun SiteContext.resolveRef(name: Name): String = "${path.removeSuffix("/")}/${name.tokens.joinToString("/")}" fun SiteData.resolveRef(name: Name): String = "${urlPath.removeSuffix("/")}/${name.tokens.joinToString("/")}"
/** /**
* Resolve a Html builder by its full name * Resolve a Html builder by its full name
*/ */
fun SiteContext.resolveHtml(name: Name): HtmlData? { fun DataTree<*>.resolveHtml(name: Name): HtmlData? {
val resolved = (data.getByType<HtmlFragment>(name) ?: data.getByType<HtmlFragment>(name + INDEX_PAGE_NAME)) val resolved = (getByType<HtmlFragment>(name) ?: getByType<HtmlFragment>(name + INDEX_PAGE_NAME))
return resolved?.takeIf { return resolved?.takeIf {
it.published //TODO add language confirmation it.published //TODO add language confirmation
@ -55,45 +55,45 @@ fun SiteContext.resolveHtml(name: Name): HtmlData? {
/** /**
* Find all Html blocks using given name/meta filter * Find all Html blocks using given name/meta filter
*/ */
fun SiteContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> = fun DataTree<*>.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
data.filterByType<HtmlFragment> { name, meta -> filterByType<HtmlFragment> { name, meta ->
predicate(name, meta) predicate(name, meta)
&& meta["published"].string != "false" && meta["published"].string != "false"
//TODO add language confirmation //TODO add language confirmation
}.asSequence().associate { it.name to it.data } }.asSequence().associate { it.name to it.data }
val SiteContext.homeRef get() = resolveRef("").removeSuffix("/") val SiteData.homeRef get() = resolveRef("").removeSuffix("/")
fun SiteContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta -> fun SiteData.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta ->
name.startsWith(baseName) && meta["content_type"].string == contentType name.startsWith(baseName) && meta["content_type"].string == contentType
} }
internal val Data<*>.published: Boolean get() = meta["published"].string != "false" internal val Data<*>.published: Boolean get() = meta["published"].string != "false"
fun SnarkPlugin.siteContext(rootUrl: String, data: DataTree<*>): SiteContext = fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData =
SiteContext(this, rootUrl, data.meta, data) SiteData(this, data, rootUrl)
fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): SiteContext { fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData {
val parsedData: DataTree<Any> = readDirectory(path) val parsedData: DataTree<Any> = readDirectory(path)
return siteContext(rootUrl, parsedData) return readData(parsedData, rootUrl)
} }
@PublishedApi @PublishedApi
internal fun SiteContext.copyWithRequestHost(request: ApplicationRequest): SiteContext { internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData {
val uri = URLBuilder( val uri = URLBuilder(
protocol = URLProtocol.createOrDefault(request.origin.scheme), protocol = URLProtocol.createOrDefault(request.origin.scheme),
host = request.host(), host = request.host(),
port = request.port(), port = request.port(),
pathSegments = path.split("/"), pathSegments = urlPath.split("/"),
) )
return copy(path = uri.buildString()) return copy(urlPath = uri.buildString())
} }
/** /**
* Substitute uri in [SiteContext] with uri in the call to properly resolve relative refs. Only host properties are substituted. * Substitute uri in [SiteData] with uri in the call to properly resolve relative refs. Only host properties are substituted.
*/ */
context(SiteContext) inline fun withRequest(request: ApplicationRequest, block: context(SiteContext) () -> Unit) { context(SiteData) inline fun withRequest(request: ApplicationRequest, block: context(SiteData) () -> Unit) {
block(copyWithRequestHost(request)) block(copyWithRequestHost(request))
} }

View File

@ -1,22 +1,25 @@
package space.kscience.snark package space.kscience.snark
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.DataTreeItem import space.kscience.dataforge.data.DataTreeItem
import space.kscience.dataforge.data.await import space.kscience.dataforge.data.await
import space.kscience.dataforge.data.getItem
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.getIndexed import space.kscience.dataforge.meta.getIndexed
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.parseAsName
import space.kscience.snark.SiteLayout.Companion.DESIGNATION_KEY import space.kscience.snark.SiteLayout.Companion.ASSETS_KEY
import space.kscience.snark.SiteLayout.Companion.INDEX_PAGE_TOKEN
import space.kscience.snark.SiteLayout.Companion.LAYOUT_KEY import space.kscience.snark.SiteLayout.Companion.LAYOUT_KEY
import java.nio.file.Path import java.nio.file.Path
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
internal fun SiteBuilder.staticFrom(rootMeta: Meta) { internal fun SiteBuilder.assetsFrom(rootMeta: Meta) {
rootMeta.getIndexed("resource".asName()).forEach { (_, meta) -> rootMeta.getIndexed("resource".asName()).forEach { (_, meta) ->
val path by meta.string() val path by meta.string()
@ -25,75 +28,109 @@ internal fun SiteBuilder.staticFrom(rootMeta: Meta) {
path?.let { resourcePath -> path?.let { resourcePath ->
//If remote path provided, use a single resource //If remote path provided, use a single resource
remotePath?.let { remotePath?.let {
staticResourceFile(it, resourcePath) assetResourceFile(it, resourcePath)
return@forEach return@forEach
} }
//otherwise use package resources //otherwise use package resources
staticResourceDirectory(resourcePath) assetResourceDirectory(resourcePath)
} }
} }
rootMeta.getIndexed("file".asName()).forEach { (_, meta) -> rootMeta.getIndexed("file".asName()).forEach { (_, meta) ->
val remotePath by meta.string { error("File remote path is not provided") } val remotePath by meta.string { error("File remote path is not provided") }
val path by meta.string { error("File path is not provided") } val path by meta.string { error("File path is not provided") }
staticFile(remotePath, Path.of(path)) assetFile(remotePath, Path.of(path))
} }
rootMeta.getIndexed("directory".asName()).forEach { (_, meta) -> rootMeta.getIndexed("directory".asName()).forEach { (_, meta) ->
val path by meta.string { error("Directory path is not provided") } val path by meta.string { error("Directory path is not provided") }
staticDirectory("", Path.of(path)) assetDirectory("", Path.of(path))
} }
} }
/** /**
* Represent pages in a [DataTree] * Recursively renders the data items in [data]. If [LAYOUT_KEY] is defined in an item, use it to load
* layout from the context, otherwise render children nodes as name segments and individual data items using [dataRenderer].
*/ */
fun SiteBuilder.data(data: DataTreeItem<*>, prefix: Name = Name.EMPTY) { fun SiteBuilder.pages(
data: DataTreeItem<*>,
dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer,
) {
val layoutMeta = data.meta[LAYOUT_KEY] val layoutMeta = data.meta[LAYOUT_KEY]
if (layoutMeta != null) { if (layoutMeta != null) {
//use layout if it is defined //use layout if it is defined
siteContext.snark.layout(layoutMeta).render(data) this.data.snark.layout(layoutMeta).render(data)
} else { } else {
when (data) { when (data) {
is DataTreeItem.Node -> { is DataTreeItem.Node -> {
data.tree.items.forEach { (token, item) -> data.tree.items.forEach { (token, item) ->
data(item, prefix + token) //Don't apply index token
} if (token == INDEX_PAGE_TOKEN) {
} pages(item, dataRenderer)
is DataTreeItem.Leaf -> { }
val item = data.data route(token.toString()) {
if (item.type == typeOf<HtmlData>() && item.meta[DESIGNATION_KEY].string == "page") { pages(item, dataRenderer)
route(prefix.tokens.joinToString(separator = "/")) {
page {
@Suppress("UNCHECKED_CAST")
val pageFragment: HtmlFragment = runBlocking { item.await() as HtmlFragment }
pageFragment.invoke(consumer)
}
staticFrom(item.meta)
} }
} }
} }
is DataTreeItem.Leaf -> {
dataRenderer.invoke(this, data.data)
}
}
data.meta[ASSETS_KEY]?.let {
assetsFrom(it)
} }
} }
//TODO watch for changes //TODO watch for changes
} }
/**
* Render all pages in a node with given name
*/
fun SiteBuilder.pages(
dataPath: Name,
remotePath: Name = dataPath,
dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer,
) {
val item = data.getItem(dataPath) ?: error("No data found by name $dataPath")
route(remotePath) {
pages(item, dataRenderer)
}
}
fun SiteBuilder.pages(
dataPath: String,
remotePath: Name = dataPath.parseAsName(),
dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer,
) {
pages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
}
fun interface SiteLayout { fun interface SiteLayout {
context(SiteBuilder) fun render(data: DataTreeItem<*>) context(SiteBuilder) fun render(item: DataTreeItem<*>)
companion object { companion object {
internal const val DESIGNATION_KEY = "designation"
const val LAYOUT_KEY = "layout" const val LAYOUT_KEY = "layout"
const val ASSETS_KEY = "assets"
val INDEX_PAGE_TOKEN = NameToken("index")
val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data ->
if (data.type == typeOf<HtmlData>()) {
page {
@Suppress("UNCHECKED_CAST")
val pageFragment: HtmlFragment = runBlocking { data.await() as HtmlFragment }
pageFragment.invoke(consumer)
}
}
}
} }
} }
object DefaultSiteLayout : SiteLayout { object DefaultSiteLayout : SiteLayout {
context(SiteBuilder) override fun render(data: DataTreeItem<*>) { context(SiteBuilder) override fun render(item: DataTreeItem<*>) {
data(data) pages(item)
} }
} }

View File

@ -1,26 +0,0 @@
package ru.mipt.spc
//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))
// }
// }
//
//}