Create joinToHtml construct
authorLanius Trolling <lanius@laniustrolling.dev>
Sat, 6 Apr 2024 17:43:03 +0000 (13:43 -0400)
committerLanius Trolling <lanius@laniustrolling.dev>
Sat, 6 Apr 2024 17:43:03 +0000 (13:43 -0400)
src/jvmMain/kotlin/info/mechyrdia/data/data.kt
src/jvmMain/kotlin/info/mechyrdia/lore/fonts.kt
src/jvmMain/kotlin/info/mechyrdia/lore/views_lore.kt
src/jvmMain/kotlin/info/mechyrdia/lore/views_prefs.kt
src/jvmMain/kotlin/info/mechyrdia/route/resource_handler.kt

index d9c7b24a6a2830c138436eb90609fbef353e4bca..bccfe32967bc602316df30c4946dfa171b595824 100644 (file)
@@ -61,7 +61,7 @@ object ConnectionHolder {
        
        fun initialize(conn: String, db: String) {
                if (clientDeferred.isCompleted)
-                       error("Cannot initialize database twice!")
+                       error("Cannot initialize database twice")
                
                MongoClient.create(
                        MongoClientSettings.builder()
index 9ddcf3e0320c22e094ca766a6783ac74b0853e1d..d23d750e4daccab0c298d33b23a457a1ccb1d335 100644 (file)
@@ -226,10 +226,10 @@ object MechyrdiaSansFont {
                        
                        val iterator = getPathIterator(null)
                        val coords = DoubleArray(6)
-                       var isInitial = true
+                       var isFirst = true
                        while (!iterator.isDone) {
-                               if (isInitial)
-                                       isInitial = false
+                               if (isFirst)
+                                       isFirst = false
                                else
                                        append(' ')
                                
index 0be3fd5a9d0b7e79a5b5b16f9dffb08f6ad67629..4cfb5b119c2d9c57b490493d9d77d2b7cdeda080 100644 (file)
@@ -42,16 +42,16 @@ suspend fun ApplicationCall.loreIntroPage(): HTML.() -> Unit {
        }
 }
 
+private val Tag.breadCrumbArrow: Unit
+       get() {
+               +Entities.nbsp
+               +Entities.gt
+               +Entities.nbsp
+       }
+
 context(ApplicationCall)
 private fun FlowContent.breadCrumbs(links: List<Pair<Root.LorePage, String>>) = p {
-       var isNext = false
-       for ((url, text) in links) {
-               if (isNext) {
-                       +Entities.nbsp
-                       +Entities.gt
-                       +Entities.nbsp
-               } else isNext = true
-               
+       links.joinToHtml(Tag::breadCrumbArrow) { (url, text) ->
                a(href = href(url)) { +text }
        }
 }
index 0f37c19d24473dd8ebebc8ecb6034cff7224dba4..99307e96b7c1645c4b0623d0ac5703c943a57059 100644 (file)
@@ -5,6 +5,7 @@ import info.mechyrdia.route.KeyedEnumSerializer
 import io.ktor.server.application.*
 import kotlinx.html.*
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
 
 @Serializable(PageThemeSerializer::class)
 enum class PageTheme(val attributeValue: String?) {
@@ -49,6 +50,44 @@ val ApplicationCall.april1stMode: April1stMode
                else -> April1stMode.DEFAULT
        }
 
+class JoinToHtmlConsumer<E>(val iterator: Iterator<E>) {
+       inline fun <T : Tag> T.invokeReceiver(separator: T.() -> Unit, body: T.(E) -> Unit) {
+               var isFirst = true
+               for (item in iterator) {
+                       if (isFirst)
+                               isFirst = false
+                       else
+                               separator()
+                       body(item)
+               }
+       }
+       
+       context(T)
+       inline operator fun <T : Tag> invoke(separator: T.() -> Unit, body: T.(E) -> Unit) {
+               invokeReceiver(separator, body)
+       }
+}
+
+val <E> Iterable<E>.joinToHtml: JoinToHtmlConsumer<E>
+       get() = JoinToHtmlConsumer(iterator())
+
+inline fun <reified E : Enum<E>> FlowOrInteractiveOrPhrasingContent.preference(inputName: String, current: E, crossinline localize: (E) -> String) {
+       val serializer = serializer<E>() as? KeyedEnumSerializer<E> ?: throw UnsupportedOperationException("Serializer for ${E::class.simpleName} has not been declared as KeyedEnumSerializer")
+       val entries = serializer.entries
+       
+       entries.joinToHtml(Tag::br) { option ->
+               label {
+                       radioInput(name = inputName) {
+                               value = serializer.getKey(option) ?: "null"
+                               required = true
+                               checked = current == option
+                       }
+                       +Entities.nbsp
+                       +localize(option)
+               }
+       }
+}
+
 suspend fun ApplicationCall.clientSettingsPage(): HTML.() -> Unit {
        attributes.put(PageDoNotCacheAttributeKey, true)
        
@@ -62,66 +101,22 @@ suspend fun ApplicationCall.clientSettingsPage(): HTML.() -> Unit {
                }
                section {
                        h2 { +"Page Theme" }
-                       label {
-                               radioInput(name = "theme") {
-                                       value = "null"
-                                       required = true
-                                       checked = theme == PageTheme.SYSTEM
-                               }
-                               +Entities.nbsp
-                               +"System Choice"
-                       }
-                       br
-                       label {
-                               radioInput(name = "theme") {
-                                       value = "light"
-                                       required = true
-                                       checked = theme == PageTheme.LIGHT
-                               }
-                               +Entities.nbsp
-                               +"Light Theme"
-                       }
-                       br
-                       label {
-                               radioInput(name = "theme") {
-                                       value = "dark"
-                                       required = true
-                                       checked = theme == PageTheme.DARK
+                       preference<PageTheme>("theme", theme) {
+                               when (it) {
+                                       PageTheme.SYSTEM -> "Chosen by Browser/System"
+                                       PageTheme.LIGHT -> "Light Theme"
+                                       PageTheme.DARK -> "Dark Theme"
                                }
-                               +Entities.nbsp
-                               +"Dark Theme"
                        }
                }
                section {
                        h2 { +"April Fools' Day Mode" }
-                       label {
-                               radioInput(name = "april1st") {
-                                       value = "default"
-                                       required = true
-                                       checked = april1st == April1stMode.DEFAULT
-                               }
-                               +Entities.nbsp
-                               +"Only on April 1st"
-                       }
-                       br
-                       label {
-                               radioInput(name = "april1st") {
-                                       value = "always"
-                                       required = true
-                                       checked = april1st == April1stMode.ALWAYS
-                               }
-                               +Entities.nbsp
-                               +"Always"
-                       }
-                       br
-                       label {
-                               radioInput(name = "april1st") {
-                                       value = "never"
-                                       required = true
-                                       checked = april1st == April1stMode.NEVER
+                       preference<April1stMode>("april1st", april1st) {
+                               when (it) {
+                                       April1stMode.DEFAULT -> "Only on April 1st"
+                                       April1stMode.ALWAYS -> "Always"
+                                       April1stMode.NEVER -> "Never"
                                }
-                               +Entities.nbsp
-                               +"Never"
                        }
                }
        }
index fab7c34940cb663903df9428cbc28787b5580558..83c856528c12190139375afd43d8c574918fbe77 100644 (file)
@@ -47,7 +47,7 @@ inline fun <reified T : ResourceReceiver<P>, reified P : Any> Route.post() {
 }
 
 abstract class KeyedEnumSerializer<E : Enum<E>>(val entries: EnumEntries<E>, val getKey: (E) -> String? = { it.name }) : KSerializer<E> {
-       override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("EnumSerializer<${entries.first()::class.qualifiedName}>", PrimitiveKind.STRING)
+       override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KeyedEnumSerializer<${entries.first()::class.qualifiedName}>", PrimitiveKind.STRING)
        
        private val inner = String.serializer().nullable
        private val keyMap = entries.associateBy { getKey(it)?.lowercase() }