it is literally working

This commit is contained in:
Adam 2022-01-04 15:09:50 +01:00
parent 841f07c847
commit a0c330384a
17 changed files with 499 additions and 63 deletions

View file

@ -1,34 +1,73 @@
package dev.adamko.kafkatorio.webmap
import io.kvision.Application
import io.kvision.CoreModule
import io.kvision.BootstrapModule
import io.kvision.BootstrapCssModule
import io.kvision.BootstrapIconsModule
import io.kvision.FontAwesomeModule
import io.kvision.BootstrapModule
import io.kvision.ChartModule
import io.kvision.CoreModule
import io.kvision.FontAwesomeModule
import io.kvision.html.div
import io.kvision.maps.Maps
import io.kvision.module
import io.kvision.panel.root
import io.kvision.redux.ReduxStore
import io.kvision.startApplication
class App : Application() {
override fun start() {
root("kvapp") {
div("Hello world")
}
class App(
reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
wsService: WebsocketService
) : Application() {
private var appState: MutableMap<String, Any> = mutableMapOf()
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState
var wsService: WebsocketService by appState
init {
this.reduxStore = reduxStore
this.wsService = wsService
}
private val kvMaps: Maps
get() = reduxStore.getState().kvMaps
override fun start(state: Map<String, Any>) {
this.appState = state.toMutableMap()
root("kvapp") {
div("Kafkatorio Web Map")
add(kvMaps)
}
}
override fun dispose(): Map<String, Any> {
return appState
}
}
fun main() {
startApplication(
::App,
module.hot,
BootstrapModule,
BootstrapCssModule,
BootstrapIconsModule,
FontAwesomeModule,
ChartModule,
CoreModule
)
startApplication(
{
val reduxStore = createFactorioReduxStore()
val wsService = WebsocketService(
"ws://localhost:9073/ws",
reduxStore,
)
App(
reduxStore = reduxStore,
wsService = wsService
)
},
module.hot,
BootstrapModule,
BootstrapCssModule,
BootstrapIconsModule,
FontAwesomeModule,
ChartModule,
CoreModule
)
}

View file

@ -0,0 +1,36 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.Colour
import dev.adamko.kafkatorio.events.schema.PlayerData
import externals.leaflet.geo.LatLng
import io.kvision.maps.Maps
import kotlin.math.roundToInt
fun Colour.toHexString(includeAlpha: Boolean = false): String {
val hexConverter: Float.() -> String = if (isDecimal()) {
Float::decimalToHex
} else {
Float::percentageToHex
}
val length = if (includeAlpha) 9 else 7
return buildString(length) {
append('#')
append(red.hexConverter())
append(green.hexConverter())
append(blue.hexConverter())
if (includeAlpha) {
append(alpha.hexConverter())
}
}
}
fun Float.decimalToHex(): String =
roundToInt().toString(16).padStart(2, ' ')
fun Float.percentageToHex(): String =
times(255).roundToInt().toString(16).padStart(2, ' ')
fun PlayerData.latLng(): LatLng = Maps.L.latLng((position.y * -1) to position.x)

View file

@ -0,0 +1,7 @@
package dev.adamko.kafkatorio.webmap
import kotlinx.serialization.json.Json
val jsonMapper = Json {
prettyPrint = true
}

View file

@ -0,0 +1,37 @@
package dev.adamko.kafkatorio.webmap
import externals.leaflet.geo.CRS
import externals.leaflet.geo.LatLng
import io.kvision.maps.DefaultTileLayers
import io.kvision.maps.Maps
import io.kvision.utils.px
//
//val polyline = Maps.L.polyline(
// listOf(
// LatLng(55, 2),
// LatLng(65, 2),
// LatLng(65, 20),
// LatLng(55, 20),
// LatLng(55, 2),
// )
//) {
// noClip = true
//}
fun createFactorioMap() = Maps {
width = 800.px
height = 800.px
margin = 10.px
configureLeafletMap {
setView(LatLng(0, 0), 13)
// DefaultTileLayers.OpenStreetMap.addTo(this)
with(options) {
crs = CRS.Simple
}
}
}

View file

@ -0,0 +1,92 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.PlayerData
import externals.leaflet.geo.LatLng
import externals.leaflet.layer.overlay.Tooltip
import externals.leaflet.layer.vector.CircleMarker
import io.kvision.maps.Maps
import io.kvision.maps.Maps.Companion.L
import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore
import io.kvision.utils.obj
import redux.RAction
fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
createReduxStore(
::factorioGameReducer,
FactorioGameState(
kvMaps = createFactorioMap(),
players = emptyMap()
)
)
data class FactorioGameState(
val kvMaps: Maps,
val players: Map<String, PlayerState>,
)
sealed class FactorioUpdate : RAction {
data class PlayerUpdate(val event: FactorioEvent<*>, val playerData: PlayerData) :
FactorioUpdate()
}
fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState =
when (update) {
is FactorioUpdate.PlayerUpdate -> {
val playerState = state.players.getOrElse(update.playerData.name) {
val newState = PlayerState(update.playerData)
state.kvMaps.leafletMap {
addLayer(newState.mapMarker)
newState.mapMarker.openPopup()
// setView(update.playerData.latLng())
}
newState
}
playerState.update(update.playerData)
val updatedPlayersState = state.players + (update.playerData.name to playerState)
state.copy(players = updatedPlayersState)
}
}
data class PlayerState(
private var playerData: PlayerData,
val mapMarker: CircleMarker = createMapMarker(playerData)
) {
fun update(playerData: PlayerData) {
this.playerData = playerData
mapMarker.setLatLng(playerData.latLng())
println("updated player ${playerData.name}")
}
companion object {
fun createMapMarker(playerData: PlayerData): CircleMarker {
val playerLatLng = playerData.latLng()
val playerMapTooltip = "${playerData.name} [$playerLatLng]"
val circle = L.circleMarker(playerLatLng) {
color = playerData.colour.toHexString(false)
fillColor = color
fillOpacity = 1f
radius = 10
className = playerData.objectName
// interactive = false
}
circle.bindTooltip(playerMapTooltip, obj<Tooltip.TooltipOptions> {
// interactive = false
})
return circle
}
}
}

View file

@ -0,0 +1,46 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
import dev.adamko.kafkatorio.events.schema.PlayerData
import io.kvision.redux.ReduxStore
import kotlinx.serialization.decodeFromString
import org.w3c.dom.MessageEvent
import org.w3c.dom.WebSocket
class WebsocketService(
private val wsUrl: String,
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
) {
private val ws = WebSocket(wsUrl)
init {
ws.onmessage = ::handleMessageEvent
}
private fun handleMessageEvent(msg: MessageEvent) {
val data = msg.data as? String
if (
data != null
&& data.trim().startsWith("{")
&& data.trim().endsWith("}")
) {
println(data.replace('\n', ' '))
val event: FactorioEvent<FactorioObjectData> = jsonMapper.decodeFromString(data)
when (val eventData = event.data) {
is PlayerData -> {
reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData))
}
else -> {}
}
} else {
println(data)
}
}
}

View file

@ -1,9 +1,10 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KVision Template</title>
<title>Kafkatorio Web Map</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.1.0/dist/superhero/bootstrap.min.css" integrity="sha256-T8h7oW5tHp4MhFhmRoDI8xjnB2ld89qSgc3s9g8T0uU=" crossorigin="anonymous">
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body>