Skip to content

Commit

Permalink
Add realtime websocket loading
Browse files Browse the repository at this point in the history
  • Loading branch information
dellisd committed Oct 5, 2023
1 parent 78a34ad commit 23dc1a6
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 18 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kotlin = "1.8.10"
kotlinx-serialization = "1.5.0"
sqldelight = "2.0.0"
ktlint = "0.48.2"
ktor = "2.1.2"
ktor = "2.3.4"
inject = "0.5.1"

copyWebpackPlugin = "9.1.0"
Expand Down
2 changes: 1 addition & 1 deletion kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2783,7 +2783,7 @@ source-map-loader@4.0.0:
iconv-lite "^0.6.3"
source-map-js "^1.0.2"

source-map-support@~0.5.20:
source-map-support@0.5.21, source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
Expand Down
23 changes: 14 additions & 9 deletions web/src/jsMain/kotlin/ca/derekellis/reroute/data/RerouteClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,23 @@ package ca.derekellis.reroute.data

import ca.derekellis.reroute.di.AppScope
import ca.derekellis.reroute.models.TransitDataBundle
import ca.derekellis.reroute.realtime.RealtimeMessage
import com.soywiz.klock.DateFormat
import com.soywiz.klock.DateTime
import com.soywiz.klock.parse
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.js.Js
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.websocket.receiveDeserialized
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.request.get
import io.ktor.client.request.head
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import me.tatarka.inject.annotations.Inject

@Inject
@AppScope
class RerouteClient {
private val client = HttpClient(Js) {
install(ContentNegotiation) {
json()
}
}
class RerouteClient(private val client: HttpClient) {

suspend fun getDataBundleDate(): DateTime {
val headers = client.head("/api/data/").headers
Expand All @@ -31,4 +28,12 @@ class RerouteClient {
}

suspend fun getDataBundle(): TransitDataBundle = client.get("/api/data/").body()

fun nextTrips(code: String): Flow<RealtimeMessage> = flow {
client.webSocket("/api/realtime/$code") {
while (true) {
emit(receiveDeserialized<RealtimeMessage>())
}
}
}
}
18 changes: 18 additions & 0 deletions web/src/jsMain/kotlin/ca/derekellis/reroute/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import ca.derekellis.reroute.ui.Presenter
import ca.derekellis.reroute.ui.ScreenWrapper
import ca.derekellis.reroute.ui.SearchScreenWrapper
import ca.derekellis.reroute.ui.View
import io.ktor.client.HttpClient
import io.ktor.client.engine.js.Js
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
import org.w3c.dom.Worker
Expand Down Expand Up @@ -51,4 +58,15 @@ abstract class AppComponent {
protected fun provideWorker(): Worker =
//language=JavaScript
js("""new Worker(new URL("./worker.js", import.meta.url))""").unsafeCast<Worker>()

@Provides
@AppScope
protected fun provideHttpClient(): HttpClient = HttpClient(Js) {
install(ContentNegotiation) {
json()
}
install(WebSockets) {
contentConverter = KotlinxWebsocketSerializationConverter(Json)
}
}
}
11 changes: 11 additions & 0 deletions web/src/jsMain/kotlin/ca/derekellis/reroute/stops/RouteSection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ca.derekellis.reroute.stops

import ca.derekellis.reroute.realtime.NextTrip

data class RouteSection(
val gtfsId: String,
val identifier: String,
val name: String,
val directionId: Int,
val nextTrips: List<NextTrip>?,
)
22 changes: 19 additions & 3 deletions web/src/jsMain/kotlin/ca/derekellis/reroute/stops/StopPresenter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import ca.derekellis.reroute.data.DataSource
import ca.derekellis.reroute.data.RerouteClient
import ca.derekellis.reroute.home.Home
import ca.derekellis.reroute.map.MapInteractionsManager
import ca.derekellis.reroute.realtime.RealtimeMessage
import ca.derekellis.reroute.ui.CollectEffect
import ca.derekellis.reroute.ui.Navigator
import ca.derekellis.reroute.ui.Presenter
Expand All @@ -17,6 +19,7 @@ import me.tatarka.inject.annotations.Inject

@Inject
class StopPresenter(
private val client: RerouteClient,
private val dataSource: DataSource,
private val navigator: Navigator,
private val mapInteractionsManager: MapInteractionsManager,
Expand All @@ -40,15 +43,18 @@ class StopPresenter(

if (routesAtStop == null || stopList == null) return StopViewModel.Loading

val realtimeData by remember { client.nextTrips(args.code) }.collectAsState(null)

val routeSections = remember(routesAtStop) {
(routesAtStop ?: emptyList()).map {
StopViewModel.Loaded.RouteSection(
RouteSection(
it.gtfsId,
it.identifier,
it.destinations[it.directionId],
it.directionId,
null,
)
}.sortedWith(compareBy<StopViewModel.Loaded.RouteSection> { it.identifier.length }.thenBy(naturalOrder()) { it.identifier })
}.sortedWith(compareBy<RouteSection> { it.identifier.length }.thenBy(naturalOrder()) { it.identifier })
}

val stop = stopList!!.firstOrNull() ?: return StopViewModel.NotFound
Expand All @@ -57,6 +63,16 @@ class StopPresenter(
mapInteractionsManager.goTo(stop)
}

return StopViewModel.Loaded(stop, routeSections)
return StopViewModel.Loaded(stop, realtimeData?.let { zipRealtimeData(it, routeSections) } ?: routeSections)
}

private fun zipRealtimeData(realtime: RealtimeMessage, routeSections: List<RouteSection>): List<RouteSection> {
val incoming = realtime.routes.associateBy { "${it.number}-${it.directionId}" }

return routeSections.map { section ->
val nextTrips = incoming["${section.identifier}-${section.directionId}"]

section.copy(nextTrips = nextTrips?.trips)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ private fun StopContent(model: StopViewModel) {
}

@Composable
private fun RouteInfo(route: StopViewModel.Loaded.RouteSection) {
private fun RouteInfo(route: RouteSection) {
Div {
H3 {
Text("${route.identifier} ${route.name}")
}
Text(route.nextTrips.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ sealed interface StopViewModel {
data class Loaded(
val stop: Stop,
val groupedRoutes: List<RouteSection>,
) : StopViewModel {
data class RouteSection(val gtfsId: String, val identifier: String, val name: String, val directionId: Int)
}
) : StopViewModel

object NotFound : StopViewModel
}
5 changes: 5 additions & 0 deletions web/webpack.config.d/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ config.devServer = {
"/api": {
target: "http://localhost:8888/",
pathRewrite: {"^/api": ""}
},
"/api/realtime": {
target: "ws://localhost:8888/realtime/",
pathRewrite: { '^/api/realtime': '' },
ws: true,
}
},
open: false
Expand Down

0 comments on commit 23dc1a6

Please sign in to comment.