forked from SPC/spc-site
Spliting site and the snark project complete
This commit is contained in:
parent
eb81d46238
commit
5e93f982f8
@ -29,6 +29,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-http-redirect:$ktorVersion")
|
implementation("io.ktor:ktor-server-http-redirect:$ktorVersion")
|
||||||
|
implementation("ch.qos.logback:logback-classic:1.2.11")
|
||||||
|
|
||||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||||
}
|
}
|
||||||
|
BIN
data/common/android-chrome-192x192.png
Normal file
BIN
data/common/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
data/common/android-chrome-512x512.png
Normal file
BIN
data/common/android-chrome-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
BIN
data/common/apple-touch-icon.png
Normal file
BIN
data/common/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
data/common/favicon-16x16.png
Normal file
BIN
data/common/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 810 B |
BIN
data/common/favicon-32x32.png
Normal file
BIN
data/common/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
data/common/favicon.ico
Normal file
BIN
data/common/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
data/common/site.webmanifest
Normal file
1
data/common/site.webmanifest
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
@ -1,7 +1,7 @@
|
|||||||
package html5up.forty
|
package html5up.forty
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import space.kscience.snark.html.Page
|
import space.kscience.snark.html.WebPage
|
||||||
|
|
||||||
|
|
||||||
internal fun FlowContent.fortyMenu() {
|
internal fun FlowContent.fortyMenu() {
|
||||||
@ -200,7 +200,7 @@ internal fun FlowContent.fortyFooter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun BODY.fortyScripts() {
|
context(WebPage) internal fun BODY.fortyScripts() {
|
||||||
script {
|
script {
|
||||||
src = resolveRef("assets/js/jquery.min.js")
|
src = resolveRef("assets/js/jquery.min.js")
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package html5up.forty
|
package html5up.forty
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import space.kscience.snark.html.Page
|
import space.kscience.snark.html.WebPage
|
||||||
|
|
||||||
context(Page) internal fun HTML.landing(){
|
context(WebPage) internal fun HTML.landing(){
|
||||||
head {
|
head {
|
||||||
title {
|
title {
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package html5up.forty
|
package html5up.forty
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import space.kscience.snark.html.Page
|
import space.kscience.snark.html.WebPage
|
||||||
|
|
||||||
context(Page) internal fun HTML.fortyPage(){
|
context(WebPage) internal fun HTML.fortyPage(){
|
||||||
head {
|
head {
|
||||||
title {
|
title {
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,8 @@ import io.ktor.server.application.log
|
|||||||
import kotlinx.css.CssBuilder
|
import kotlinx.css.CssBuilder
|
||||||
import kotlinx.html.CommonAttributeGroupFacade
|
import kotlinx.html.CommonAttributeGroupFacade
|
||||||
import kotlinx.html.style
|
import kotlinx.html.style
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.snark.SnarkEnvironment
|
||||||
import space.kscience.dataforge.context.fetch
|
import space.kscience.snark.ktor.site
|
||||||
import space.kscience.snark.html.SnarkPlugin
|
|
||||||
import space.kscience.snark.ktor.snarkSite
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@ -49,11 +47,6 @@ const val BUILD_DATE_FILE = "/buildDate"
|
|||||||
fun Application.spcModule() {
|
fun Application.spcModule() {
|
||||||
// install(HttpsRedirect)
|
// install(HttpsRedirect)
|
||||||
|
|
||||||
val context = Context("spc-site") {
|
|
||||||
plugin(SnarkPlugin)
|
|
||||||
}
|
|
||||||
val snark = context.fetch(SnarkPlugin)
|
|
||||||
|
|
||||||
val dataPath = Path.of("data")
|
val dataPath = Path.of("data")
|
||||||
|
|
||||||
// Clear data directory if it is outdated
|
// Clear data directory if it is outdated
|
||||||
@ -88,20 +81,20 @@ fun Application.spcModule() {
|
|||||||
dataPath.resolve(DEPLOY_DATE_FILE).writeText(date)
|
dataPath.resolve(DEPLOY_DATE_FILE).writeText(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
snarkSite(snark) {
|
SnarkEnvironment.default.site {
|
||||||
val homeDataPath = resolveData(
|
val homeDataPath = resolveData(
|
||||||
this@spcModule.javaClass.getResource("/home")!!.toURI(),
|
this@spcModule.javaClass.getResource("/home")!!.toURI(),
|
||||||
dataPath / "home"
|
dataPath / "home"
|
||||||
)
|
)
|
||||||
|
|
||||||
spcHome(rootPath = homeDataPath)
|
spcHome(dataPath = homeDataPath)
|
||||||
|
|
||||||
val mastersDataPath = resolveData(
|
val mastersDataPath = resolveData(
|
||||||
this@spcModule.javaClass.getResource("/magprog")!!.toURI(),
|
this@spcModule.javaClass.getResource("/magprog")!!.toURI(),
|
||||||
dataPath / "magprog"
|
dataPath / "magprog"
|
||||||
)
|
)
|
||||||
|
|
||||||
spcMaster(dataPath = mastersDataPath)
|
spcMasters(dataPath = mastersDataPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(Page) private fun FlowContent.spcSpotlightContent(
|
context(WebPage) private fun FlowContent.spcSpotlightContent(
|
||||||
landing: HtmlData,
|
landing: HtmlData,
|
||||||
content: Map<Name, HtmlData>,
|
content: Map<Name, HtmlData>,
|
||||||
) {
|
) {
|
||||||
|
@ -15,7 +15,7 @@ import java.nio.file.Path
|
|||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
|
||||||
context(Page) internal fun HTML.spcPageContent(
|
context(WebPage) 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,
|
||||||
@ -65,7 +65,7 @@ internal val FortyDataRenderer: DataRenderer = { name, data ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
context(Page) private fun HTML.spcHome() {
|
context(WebPage) private fun HTML.spcHome() {
|
||||||
spcHead()
|
spcHead()
|
||||||
body("is-preload") {
|
body("is-preload") {
|
||||||
wrapper {
|
wrapper {
|
||||||
@ -252,13 +252,14 @@ context(Page) private fun HTML.spcHome() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun SiteBuilder.spcHome(rootPath: Path, prefix: Name = Name.EMPTY) {
|
internal fun SiteBuilder.spcHome(dataPath: Path, prefix: Name = Name.EMPTY) {
|
||||||
|
|
||||||
val homePageData = snark.readDirectory(rootPath.resolve("content"))
|
val homePageData = snark.readDirectory(dataPath.resolve("content"))
|
||||||
|
|
||||||
route(prefix, homePageData, setAsRoot = true) {
|
route(prefix, homePageData, setAsRoot = true) {
|
||||||
assetDirectory("assets", rootPath.resolve("assets"))
|
file(dataPath.resolve("assets"))
|
||||||
assetDirectory("images", rootPath.resolve("images"))
|
file(dataPath.resolve("images"))
|
||||||
|
file(dataPath.resolve("../common"), "")
|
||||||
|
|
||||||
page { spcHome() }
|
page { spcHome() }
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import space.kscience.dataforge.names.plus
|
|||||||
import space.kscience.dataforge.names.withIndex
|
import space.kscience.dataforge.names.withIndex
|
||||||
import space.kscience.snark.*
|
import space.kscience.snark.*
|
||||||
import space.kscience.snark.html.*
|
import space.kscience.snark.html.*
|
||||||
import space.kscience.snark.html.Page
|
import space.kscience.snark.html.WebPage
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
@ -37,14 +37,14 @@ import kotlin.collections.set
|
|||||||
private val HtmlData.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string
|
private val HtmlData.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string
|
||||||
private val HtmlData.name: String get() = meta["name"].string ?: error("Name not found")
|
private val HtmlData.name: String get() = meta["name"].string ?: error("Name not found")
|
||||||
|
|
||||||
context(Page) class MagProgSection(
|
context(WebPage) class MagProgSection(
|
||||||
val id: String,
|
val id: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val style: String,
|
val style: String,
|
||||||
val content: FlowContent.() -> Unit,
|
val content: FlowContent.() -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
context(Page) private fun wrapSection(
|
context(WebPage) private fun wrapSection(
|
||||||
id: String,
|
id: String,
|
||||||
title: String,
|
title: String,
|
||||||
sectionContent: FlowContent.() -> Unit,
|
sectionContent: FlowContent.() -> Unit,
|
||||||
@ -55,7 +55,7 @@ context(Page) private fun wrapSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) private fun wrapSection(
|
context(WebPage) private fun wrapSection(
|
||||||
block: HtmlData,
|
block: HtmlData,
|
||||||
idOverride: String? = null,
|
idOverride: String? = null,
|
||||||
): MagProgSection = wrapSection(
|
): MagProgSection = wrapSection(
|
||||||
@ -73,7 +73,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(Page) private fun FlowContent.programSection() {
|
context(WebPage) private fun FlowContent.programSection() {
|
||||||
val programBlock = data.resolveHtml(PROGRAM_PATH)!!
|
val programBlock = data.resolveHtml(PROGRAM_PATH)!!
|
||||||
val recommendedBlock = data.resolveHtml(RECOMMENDED_COURSES_PATH)!!
|
val recommendedBlock = data.resolveHtml(RECOMMENDED_COURSES_PATH)!!
|
||||||
div("inner") {
|
div("inner") {
|
||||||
@ -90,7 +90,7 @@ context(Page) private fun FlowContent.programSection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) private fun FlowContent.partners() {
|
context(WebPage) 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") {
|
||||||
@ -120,7 +120,7 @@ context(Page) private fun FlowContent.partners() {
|
|||||||
// val photo: String? by meta.string()
|
// val photo: String? by meta.string()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
context(Page) private fun FlowContent.team() {
|
context(WebPage) private fun FlowContent.team() {
|
||||||
val team = data.findByContentType("magprog_team").values.sortedBy { it.order }
|
val team = data.findByContentType("magprog_team").values.sortedBy { it.order }
|
||||||
|
|
||||||
div("inner") {
|
div("inner") {
|
||||||
@ -175,7 +175,7 @@ context(Page) private fun FlowContent.team() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) private fun FlowContent.mentors() {
|
context(WebPage) private fun FlowContent.mentors() {
|
||||||
val mentors = data.findByContentType("magprog_mentor").entries.sortedBy { it.value.id }
|
val mentors = data.findByContentType("magprog_mentor").entries.sortedBy { it.value.id }
|
||||||
|
|
||||||
div("inner") {
|
div("inner") {
|
||||||
@ -213,7 +213,7 @@ context(Page) private fun FlowContent.mentors() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun HTML.magProgHead(title: String) {
|
context(WebPage) internal fun HTML.magProgHead(title: String) {
|
||||||
head {
|
head {
|
||||||
this.title = title
|
this.title = title
|
||||||
meta {
|
meta {
|
||||||
@ -237,10 +237,31 @@ context(Page) internal fun HTML.magProgHead(title: String) {
|
|||||||
href = resolveRef("assets/css/noscript.css")
|
href = resolveRef("assets/css/noscript.css")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
link {
|
||||||
|
rel = "apple-touch-icon"
|
||||||
|
sizes = "180x180"
|
||||||
|
href = "/apple-touch-icon.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "icon"
|
||||||
|
type = "image/png"
|
||||||
|
sizes = "32x32"
|
||||||
|
href = "/favicon-32x32.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "icon"
|
||||||
|
type = "image/png"
|
||||||
|
sizes = "16x16"
|
||||||
|
href = "/favicon-16x16.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "manifest"
|
||||||
|
href = "/site.webmanifest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun BODY.magProgFooter() {
|
context(WebPage) internal fun BODY.magProgFooter() {
|
||||||
footer("wrapper style1-alt") {
|
footer("wrapper style1-alt") {
|
||||||
id = "footer"
|
id = "footer"
|
||||||
div("inner") {
|
div("inner") {
|
||||||
@ -281,13 +302,14 @@ context(Page) internal fun BODY.magProgFooter() {
|
|||||||
|
|
||||||
context(SnarkContext) private val HtmlData.mentorPageId get() = "mentor-${id}"
|
context(SnarkContext) private val HtmlData.mentorPageId get() = "mentor-${id}"
|
||||||
|
|
||||||
internal fun SiteBuilder.spcMaster(dataPath: Path, prefix: Name = "magprog".asName()) {
|
internal fun SiteBuilder.spcMasters(dataPath: Path, prefix: Name = "magprog".asName()) {
|
||||||
|
|
||||||
val magProgData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
|
val magProgData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
|
||||||
|
|
||||||
route(prefix, magProgData, setAsRoot = true) {
|
route(prefix, magProgData, setAsRoot = true) {
|
||||||
assetDirectory("assets", dataPath.resolve("assets"))
|
file(dataPath.resolve("assets"))
|
||||||
assetDirectory("images", dataPath.resolve("images"))
|
file(dataPath.resolve("images"))
|
||||||
|
file(dataPath.resolve("../common"), "")
|
||||||
|
|
||||||
page {
|
page {
|
||||||
val sections = listOf<MagProgSection>(
|
val sections = listOf<MagProgSection>(
|
@ -1,14 +1,14 @@
|
|||||||
package ru.mipt.spc
|
package ru.mipt.spc
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import space.kscience.snark.html.Page
|
import space.kscience.snark.html.WebPage
|
||||||
import space.kscience.snark.html.homeRef
|
import space.kscience.snark.html.homeRef
|
||||||
import space.kscience.snark.html.resolvePageRef
|
import space.kscience.snark.html.resolvePageRef
|
||||||
|
|
||||||
|
|
||||||
internal const val SPC_TITLE = "Scientific Programming Centre"
|
internal const val SPC_TITLE = "Scientific Programming Centre"
|
||||||
|
|
||||||
context(Page) internal fun HTML.spcHead(title: String = SPC_TITLE) {
|
context(WebPage) internal fun HTML.spcHead(title: String = SPC_TITLE) {
|
||||||
head {
|
head {
|
||||||
title {
|
title {
|
||||||
+title
|
+title
|
||||||
@ -24,10 +24,31 @@ context(Page) internal fun HTML.spcHead(title: String = SPC_TITLE) {
|
|||||||
noScript {
|
noScript {
|
||||||
link(rel = "stylesheet", href = resolveRef("assets/css/noscript.css"))
|
link(rel = "stylesheet", href = resolveRef("assets/css/noscript.css"))
|
||||||
}
|
}
|
||||||
|
link {
|
||||||
|
rel = "apple-touch-icon"
|
||||||
|
sizes = "180x180"
|
||||||
|
href = "/apple-touch-icon.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "icon"
|
||||||
|
type = "image/png"
|
||||||
|
sizes = "32x32"
|
||||||
|
href = "/favicon-32x32.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "icon"
|
||||||
|
type = "image/png"
|
||||||
|
sizes = "16x16"
|
||||||
|
href = "/favicon-16x16.png"
|
||||||
|
}
|
||||||
|
link {
|
||||||
|
rel = "manifest"
|
||||||
|
href = "/site.webmanifest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun FlowContent.spcHomeMenu() {
|
context(WebPage) internal fun FlowContent.spcHomeMenu() {
|
||||||
nav {
|
nav {
|
||||||
id = "menu"
|
id = "menu"
|
||||||
ul("links") {
|
ul("links") {
|
||||||
@ -39,7 +60,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() {
|
|||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
a {
|
a {
|
||||||
href = resolvePageRef("magprog")
|
href = resolvePageRef("magprog.index")
|
||||||
+"""Master"""
|
+"""Master"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +72,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() {
|
|||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
a {
|
a {
|
||||||
href = resolvePageRef("consulting")
|
href = resolvePageRef("consulting.index")
|
||||||
+"""Consulting"""
|
+"""Consulting"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,7 +100,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun FlowContent.spcFooter() {
|
context(WebPage) internal fun FlowContent.spcFooter() {
|
||||||
footer {
|
footer {
|
||||||
id = "footer"
|
id = "footer"
|
||||||
div("inner") {
|
div("inner") {
|
||||||
@ -129,7 +150,7 @@ context(Page) internal fun FlowContent.spcFooter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Page) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
|
context(WebPage) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
|
||||||
div {
|
div {
|
||||||
id = "wrapper"
|
id = "wrapper"
|
||||||
// Header
|
// Header
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
package ru.mipt.spc
|
package ru.mipt.spc
|
||||||
|
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.snark.SnarkEnvironment
|
||||||
import space.kscience.dataforge.context.fetch
|
import space.kscience.snark.html.static
|
||||||
import space.kscience.snark.html.SnarkPlugin
|
|
||||||
import space.kscience.snark.html.renderStatic
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.toPath
|
import kotlin.io.path.toPath
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
Global.fetch(SnarkPlugin).renderStatic(Path.of("build/out")) {
|
SnarkEnvironment.default.static(Path.of("build/out")) {
|
||||||
spcHome(rootPath = javaClass.getResource("/home")!!.toURI().toPath())
|
spcHome(dataPath = javaClass.getResource("/home")!!.toURI().toPath())
|
||||||
spcMaster(dataPath = javaClass.getResource("/magprog")!!.toURI().toPath())
|
spcMasters(dataPath = javaClass.getResource("/magprog")!!.toURI().toPath())
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user