Coder Social home page Coder Social logo

Comments (6)

bmc08gt avatar bmc08gt commented on July 26, 2024 1

The Voyager integration module has no support to web history and the version in javascript module is experimental. My knowledge in browser history is too limited. I made based on MDN documentation.

About your sample code it is correct. We need do on each voyager navigation a browser history update.

To solve starting from a GET call, it is not too simple because you need to tell the Voyager about the path. Also check for history to avoid clean it. Something like

Maybe doing:

fun main() {
    val someLocker = ...
    val router = routing (...) { ... }
    
    VoyagerRouting(
        routing = router,
        content = {
            CurrentScreen()
            SideEffect {
                someLocker.unLock()
            }
        }
    )
    
    // Is onpageshow called on browser or tab opened? I don't know.
    window.onpageshow = {
        someLocker.wait()
        val location = window.location
        // https://developer.mozilla.org/en-US/docs/Web/API/Location#examples
        val path = location.pathname + location.search + location.hash
        router.push|replace(path = path)
    }
}

yep I actually updated the state manager for JS to match what the JS integration has more closely. I turned it into an expect/actual to make it work cross-platform.

internal expect object VoyagerRoutingStateManager {

    fun init(routing: Routing, initialScreen: Screen)

    suspend fun replaceAll(
        call: ApplicationCall,
        routing: Routing,
    )
}

@Composable
public fun VoyagerRouting(
    routing: Routing,
    initialScreen: Screen,
    disposeBehavior: NavigatorDisposeBehavior = NavigatorDisposeBehavior(),
    onBackPressed: OnBackPressed = { true },
    key: String = compositionUniqueId(),
    content: NavigatorContent = { CurrentScreen() },
) {
    CompositionLocalProvider(LocalVoyagerRouting provides routing) {
        DisposableEffect(routing) {
            VoyagerRoutingStateManager.init(routing, initialScreen)
            onDispose {
                routing.dispose()
            }
        }

        Navigator(
            screen = initialScreen,
            disposeBehavior = disposeBehavior,
            onBackPressed = onBackPressed,
            key = key,
        ) { navigator ->
            SideEffect {
                routing.application.voyagerNavigator = navigator
            }
            content(navigator)
        }
    }
}

with JS being:

internal actual object VoyagerRoutingStateManager {

    actual fun init(routing: Routing, initialScreen: Screen) {
        // First time or page refresh we try continue from last state
        val notified = routing.tryNotifyTheRoute(state = window.history.state)

        resetOnPopStateEvent(routing)

        if (!notified) {
            val location = window.location
            val path = location.pathname + location.search + location.hash
            println(path)
            routing.push(path = path)
        }
    }

    actual suspend fun replaceAll(
        call: ApplicationCall,
        routing: Routing,
    ) {
        while (true) {
            window.history.replaceState(
                title = "",
                url = null,
                data = null,
            )
            window.history.go(-1)
            val forceBreak =
                runCatching {
                    withTimeout(1_000) {
                        suspendCoroutine { continuation ->
                            window.onpopstate = { event ->
                                val state = event.state.deserialize()
                                continuation.resume(state == null)
                            }
                        }
                    }
                }.getOrDefault(true)
            if (forceBreak) {
                break
            }
        }

        window.history.replaceState(
            title = "routing",
            url = call.uri,
            data = call.serialize(),
        )

        resetOnPopStateEvent(routing)
    }

    private fun resetOnPopStateEvent(routing: Routing) {
        window.onpopstate = { event ->
            routing.tryNotifyTheRoute(state = event.state)
        }
    }

    private fun Routing.tryNotifyTheRoute(state: Any?): Boolean {
        val javascriptState = state.deserialize() ?: return false
        val call =
            ApplicationCall(
                application = application,
                name = javascriptState.name,
                uri = javascriptState.uri,
                routeMethod = RouteMethod.parse(javascriptState.routeMethod),
                parameters = parametersOf(javascriptState.parameters),
            )
        call.neglect = true
        execute(call)

        window.history.replaceState(
            title = "routing",
            url = call.uri,
            data = call.serialize(),
        )
        push(javascriptState.uri)
        return true
    }
}

from kotlin-routing.

bmc08gt avatar bmc08gt commented on July 26, 2024

Was able to kang the browser history from js integration into voyagers and get it partially working.

However GET requests for a screen/path don't resolve yet (push's update the history).

in VoyagerRoutingBuilder.kt:

@KtorDsl
public fun Route.screen(body: suspend PipelineContext<Unit, ApplicationCall>.() -> Screen) {
    handle {
        screen {
            body(this)
        }

+       call.handleScreenTransition()
    }
}

+ expect suspend fun ApplicationCall.handleScreenTransition()

in my jsMain

actual suspend fun ApplicationCall.handleScreenTransition() {
    when (routeMethod) {
        RouteMethod.Push -> {
            window.history.pushState(
                title = "routing",
                url = uri,
                data = serialize()
            )
        }
        RouteMethod.Replace -> {
            window.history.replaceState(
                title = "routing",
                url = uri,
                data = serialize(),
            )
        }
        RouteMethod.ReplaceAll -> {
            while (true) {
                window.history.replaceState(
                    title = "",
                    url = null,
                    data = null,
                )
                window.history.go(-1)
                val forceBreak =
                    runCatching {
                        withTimeout(1_000) {
                            suspendCoroutine { continuation ->
                                window.onpopstate = { event ->
                                    val state = event.state.deserialize()
                                    continuation.resume(state == null)
                                }
                            }
                        }
                    }.getOrDefault(true)
                if (forceBreak) {
                    break
                }
            }

            window.history.replaceState(
                title = "routing",
                url = uri,
                data = serialize(),
            )
        }
    }
}

@Serializable
internal data class JavascriptRoutingState(
    val routeMethod: String,
    val name: String,
    val uri: String,
    val parameters: Map<String, List<String>>,
)

internal fun ApplicationCall.serialize(): String {
    val state =
        JavascriptRoutingState(
            routeMethod = routeMethod.value,
            name = name,
            uri = uri,
            parameters = parameters.toMap(),
        )
    return Json.encodeToString(state)
}

internal fun Any?.deserialize(): JavascriptRoutingState? =
    when (this) {
        is String -> toState()
        else -> null
    }

private fun String.toState(): JavascriptRoutingState? =
    runCatching {
        Json.decodeFromString<JavascriptRoutingState>(this)
    }.getOrNull()

and in my mobileMain (custom group for ios/android)

actual suspend fun ApplicationCall.handleScreenTransition() {}

from kotlin-routing.

programadorthi avatar programadorthi commented on July 26, 2024

The Voyager integration module has no support to web history and the version in javascript module is experimental.
My knowledge in browser history is too limited. I made based on MDN documentation.

About your sample code it is correct. We need do on each voyager navigation a browser history update.

To solve starting from a GET call, it is not too simple because you need to tell the Voyager about the path. Also check for history to avoid clean it. Something like

Maybe doing:

fun main() {
    val someLocker = ...
    val router = routing (...) { ... }
    
    VoyagerRouting(
        routing = router,
        content = {
            CurrentScreen()
            SideEffect {
                someLocker.unLock()
            }
        }
    )
    
    // Is onpageshow called on browser or tab opened? I don't know.
    window.onpageshow = {
        someLocker.wait()
        val location = window.location
        // https://developer.mozilla.org/en-US/docs/Web/API/Location#examples
        val path = location.pathname + location.search + location.hash
        router.push|replace(path = path)
    }
}

from kotlin-routing.

programadorthi avatar programadorthi commented on July 26, 2024

Nice. I updated the javascript integration to support history modes. Hash /#/, Html5 History and Memory.
Most of these behaviors I will export to voyager integration too.

from kotlin-routing.

programadorthi avatar programadorthi commented on July 26, 2024

Checkout release 0.0.15.

from kotlin-routing.

programadorthi avatar programadorthi commented on July 26, 2024

Latest releases starting from 0.0.17 should have support.
There is no support for WASM but it will be tracked in another issue.

from kotlin-routing.

Related Issues (4)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.