it is literally working
This commit is contained in:
parent
841f07c847
commit
a0c330384a
17 changed files with 499 additions and 63 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
||||
|
||||
plugins {
|
||||
id("dev.adamko.kafkatorio.lang.kotlin-js")
|
||||
kotlin("plugin.serialization")
|
||||
// kotlin("js") // version kotlinVersion
|
||||
id("io.kvision") version "5.6.1"
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ kotlin {
|
|||
browser {
|
||||
runTask {
|
||||
outputFileName = "main.bundle.js"
|
||||
sourceMaps = false
|
||||
sourceMaps = true
|
||||
devServer = KotlinWebpackConfig.DevServer(
|
||||
open = false,
|
||||
port = 3000,
|
||||
|
|
@ -37,31 +37,54 @@ kotlin {
|
|||
}
|
||||
|
||||
sourceSets {
|
||||
val kvisionVersion: String = libs.versions.kvision.get()
|
||||
|
||||
all {
|
||||
languageSettings.optIn("kotlin.RequiresOptIn")
|
||||
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
}
|
||||
|
||||
main {
|
||||
dependencies {
|
||||
|
||||
implementation(projects.modules.eventsSchema)
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.3")
|
||||
|
||||
implementation("io.kvision:kvision:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-bootstrap:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-bootstrap-css:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-bootstrap-icons:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-fontawesome:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-state:$kvisionVersion")
|
||||
implementation("io.kvision:kvision-chart:$kvisionVersion")
|
||||
kvision("kvision")
|
||||
kvision("kvision-bootstrap")
|
||||
kvision("kvision-bootstrap-css")
|
||||
kvision("kvision-bootstrap-icons")
|
||||
kvision("kvision-fontawesome")
|
||||
kvision("kvision-state")
|
||||
kvision("kvision-chart")
|
||||
kvision("kvision-maps")
|
||||
kvision("kvision-redux")
|
||||
kvision("kvision-state-flow")
|
||||
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
}
|
||||
|
||||
val webDir = file("src/main/web")
|
||||
resources.srcDir(webDir)
|
||||
}
|
||||
|
||||
test {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
implementation("io.kvision:kvision-testutils:$kvisionVersion")
|
||||
|
||||
kvision("kvision-testutils")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun KotlinDependencyHandler.kvision(
|
||||
module: String,
|
||||
version: Provider<String> = libs.versions.kvision,
|
||||
configure: ExternalModuleDependency.() -> Unit = {
|
||||
isChanging = true
|
||||
}
|
||||
) {
|
||||
implementation("io.kvision:$module:${version.get()}", configure)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
val jsonMapper = Json {
|
||||
prettyPrint = true
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue