From 98f9ddf4eb553a7c1eebaf9142bf4b80b87da436 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Fri, 22 Dec 2023 00:18:39 -0300 Subject: [PATCH] feat: initial javascript support --- .../routing/compose/ComposeRoutingBuilder.kt | 22 +++++++-- .../routing/core/StackManager.kt | 2 +- .../routing/core/StackNeglectAttribute.kt | 2 +- .../routing/core/StackRouting.kt | 16 +++---- javascript/.gitignore | 1 + javascript/build.gradle.kts | 18 +++++++ javascript/gradle.properties | 2 + .../routing/javascript/JavascriptRouting.kt | 47 +++++++++++++++++++ .../javascript/JavascriptRoutingAttribute.kt | 22 +++++++++ .../javascript/JavascriptRoutingBuilder.kt | 41 ++++++++++++++++ settings.gradle.kts | 1 + 11 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 javascript/.gitignore create mode 100644 javascript/build.gradle.kts create mode 100644 javascript/gradle.properties create mode 100644 javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRouting.kt create mode 100644 javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingAttribute.kt create mode 100644 javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingBuilder.kt diff --git a/compose/common/src/dev/programadorthi/routing/compose/ComposeRoutingBuilder.kt b/compose/common/src/dev/programadorthi/routing/compose/ComposeRoutingBuilder.kt index 3de0f3d..17b4787 100644 --- a/compose/common/src/dev/programadorthi/routing/compose/ComposeRoutingBuilder.kt +++ b/compose/common/src/dev/programadorthi/routing/compose/ComposeRoutingBuilder.kt @@ -9,6 +9,7 @@ import dev.programadorthi.routing.core.application.application import dev.programadorthi.routing.core.application.call import dev.programadorthi.routing.core.previousCall import dev.programadorthi.routing.core.route +import dev.programadorthi.routing.core.toNeglect import io.ktor.util.KtorDsl import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.execute @@ -37,10 +38,23 @@ public fun Route.composable( if (call.routeMethod != StackRouteMethod.Pop) { call.content = { body(this) } } else { - // Checking for previous ApplicationCall and redirecting to it - val previous = previousCall() - if (previous != null) { - application.execute(previous) + // Checking for previous ApplicationCall to recompose it + val popDestination = previousCall() + if (popDestination != null) { + // We need do some things here: + // 1. Use previous call name, uri and route method + // 2. Put pop call attributes and parameters to previous call consume + // 3. Neglect the call to avoid put again on the stack + application.execute( + ApplicationCall( + application = application, + name = popDestination.name, + uri = popDestination.uri, + routeMethod = popDestination.routeMethod, + attributes = call.attributes, + parameters = call.parameters, + ).toNeglect(neglect = true) + ) } } } diff --git a/core-stack/common/src/dev/programadorthi/routing/core/StackManager.kt b/core-stack/common/src/dev/programadorthi/routing/core/StackManager.kt index 46a1297..27c5ce3 100644 --- a/core-stack/common/src/dev/programadorthi/routing/core/StackManager.kt +++ b/core-stack/common/src/dev/programadorthi/routing/core/StackManager.kt @@ -96,7 +96,7 @@ internal class StackManager( fun update(call: ApplicationCall) { // Check if route should be out of the stack - if (call.stackNeglect || call.routeMethod == StackRouteMethod.Pop) { + if (call.neglect || call.routeMethod == StackRouteMethod.Pop) { return } diff --git a/core-stack/common/src/dev/programadorthi/routing/core/StackNeglectAttribute.kt b/core-stack/common/src/dev/programadorthi/routing/core/StackNeglectAttribute.kt index ccdb039..30e1835 100644 --- a/core-stack/common/src/dev/programadorthi/routing/core/StackNeglectAttribute.kt +++ b/core-stack/common/src/dev/programadorthi/routing/core/StackNeglectAttribute.kt @@ -5,7 +5,7 @@ import io.ktor.util.AttributeKey internal val StackNeglectAttributeKey: AttributeKey = AttributeKey("StackNeglectAttributeKey") -public var ApplicationCall.stackNeglect: Boolean +public var ApplicationCall.neglect: Boolean get() = attributes.contains(StackNeglectAttributeKey) internal set(value) = if (value) { attributes.put(StackNeglectAttributeKey, Unit) diff --git a/core-stack/common/src/dev/programadorthi/routing/core/StackRouting.kt b/core-stack/common/src/dev/programadorthi/routing/core/StackRouting.kt index b15d1e3..3637caa 100644 --- a/core-stack/common/src/dev/programadorthi/routing/core/StackRouting.kt +++ b/core-stack/common/src/dev/programadorthi/routing/core/StackRouting.kt @@ -28,7 +28,7 @@ public fun Routing.push( uri = path, parameters = parameters, routeMethod = StackRouteMethod.Push, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } @@ -44,7 +44,7 @@ public fun Routing.pushNamed( name = name, parameters = parameters, routeMethod = StackRouteMethod.Push, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } @@ -60,7 +60,7 @@ public fun Routing.replace( uri = path, parameters = parameters, routeMethod = StackRouteMethod.Replace, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } @@ -76,7 +76,7 @@ public fun Routing.replaceAll( uri = path, parameters = parameters, routeMethod = StackRouteMethod.ReplaceAll, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } @@ -92,7 +92,7 @@ public fun Routing.replaceNamed( name = name, parameters = parameters, routeMethod = StackRouteMethod.Replace, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } @@ -108,11 +108,11 @@ public fun Routing.replaceAllNamed( name = name, parameters = parameters, routeMethod = StackRouteMethod.ReplaceAll, - ).tryNeglect(neglect) + ).toNeglect(neglect) ) } -private fun ApplicationCall.tryNeglect(neglect: Boolean): ApplicationCall { - stackNeglect = neglect +public fun ApplicationCall.toNeglect(neglect: Boolean): ApplicationCall { + this.neglect = neglect return this } diff --git a/javascript/.gitignore b/javascript/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/javascript/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/javascript/build.gradle.kts b/javascript/build.gradle.kts new file mode 100644 index 0000000..288c7a8 --- /dev/null +++ b/javascript/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("multiplatform") + id("org.jlleitschuh.gradle.ktlint") + id("org.jetbrains.kotlinx.kover") + alias(libs.plugins.maven.publish) +} + +applyBasicSetup() + +kotlin { + sourceSets { + commonMain { + dependencies { + api(projects.core) + } + } + } +} diff --git a/javascript/gradle.properties b/javascript/gradle.properties new file mode 100644 index 0000000..f5fb2b5 --- /dev/null +++ b/javascript/gradle.properties @@ -0,0 +1,2 @@ +POM_NAME=Javascript +POM_ARTIFACT_ID=javascript \ No newline at end of file diff --git a/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRouting.kt b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRouting.kt new file mode 100644 index 0000000..f398038 --- /dev/null +++ b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRouting.kt @@ -0,0 +1,47 @@ +package dev.programadorthi.routing.javascript + +import dev.programadorthi.routing.core.Routing +import dev.programadorthi.routing.core.application +import dev.programadorthi.routing.core.call +import kotlinx.browser.window +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import kotlinx.dom.clear +import org.w3c.dom.Element +import org.w3c.dom.PopStateEvent +import kotlin.js.Json + +internal const val METHOD_KEY = "method" +internal const val URI_KEY = "uri" + +public fun render( + routing: Routing, + root: Element, +) { + with(routing.application) { + routingFlow = MutableStateFlow(null) + + launch { + routingFlow.collect { child -> + if (child != null) { + root.clear() + root.appendChild(child) + } + } + } + } + + window.onpopstate = { event -> + onPopState(routing = routing, event = event) + } +} + +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") +private fun onPopState(routing: Routing, event: PopStateEvent) { + val json = event.state as? Json ?: return + val uri = json[URI_KEY] as? String + val method = json[METHOD_KEY] as? String + if (uri.isNullOrBlank() || method.isNullOrBlank()) return + // TODO: add route method and parameters support + routing.call(uri = uri) +} diff --git a/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingAttribute.kt b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingAttribute.kt new file mode 100644 index 0000000..fcfa37b --- /dev/null +++ b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingAttribute.kt @@ -0,0 +1,22 @@ +package dev.programadorthi.routing.javascript + +import dev.programadorthi.routing.core.application.Application +import dev.programadorthi.routing.core.application.ApplicationCall +import io.ktor.util.AttributeKey +import kotlinx.coroutines.flow.MutableStateFlow +import org.w3c.dom.Node + +internal val JavascriptRoutingAttributeKey: AttributeKey> = + AttributeKey("JavascriptRoutingAttributeKey") + +internal var Application.routingFlow: MutableStateFlow + get() = attributes[JavascriptRoutingAttributeKey] + internal set(value) { + attributes.put(JavascriptRoutingAttributeKey, value) + } + +internal var ApplicationCall.destination: Node? + get() = application.routingFlow.value + internal set(value) { + application.routingFlow.value = value + } diff --git a/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingBuilder.kt b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingBuilder.kt new file mode 100644 index 0000000..4c60d7b --- /dev/null +++ b/javascript/js/src/dev/programadorthi/routing/javascript/JavascriptRoutingBuilder.kt @@ -0,0 +1,41 @@ +package dev.programadorthi.routing.javascript + +import dev.programadorthi.routing.core.Route +import dev.programadorthi.routing.core.RouteMethod +import dev.programadorthi.routing.core.application.ApplicationCall +import dev.programadorthi.routing.core.application.call +import dev.programadorthi.routing.core.route +import io.ktor.util.KtorDsl +import io.ktor.util.pipeline.PipelineContext +import kotlinx.browser.window +import org.w3c.dom.Element +import kotlin.js.json + +@KtorDsl +public fun Route.jsRoute( + path: String, + name: String? = null, + body: PipelineContext.() -> Element, +): Route = route(path = path, name = name) { jsRoute(body) } + +@KtorDsl +public fun Route.jsRoute( + path: String, + method: RouteMethod, + name: String? = null, + body: PipelineContext.() -> Element, +): Route = route(path = path, name = name, method = method) { jsRoute(body) } + +@KtorDsl +public fun Route.jsRoute( + body: PipelineContext.() -> Element, +) { + handle { + call.destination = body(this) + val data = json( + METHOD_KEY to call.routeMethod.value, + URI_KEY to call.uri, + ) + window.history.pushState(data = data, title = "", url = call.uri) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0a3c4fb..639b3a1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include(":core") include(":core-stack") include(":events") include(":events-resources") +include(":javascript") include(":resources") include(":resources-stack") include(":sessions")