initial hacky implementation of multi-server support in the frontend

This commit is contained in:
Adam 2022-09-01 23:19:59 +02:00
parent 60d61752b3
commit 8a6c49a74b
14 changed files with 452 additions and 161 deletions

View file

@ -1,9 +1,10 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.schema.packets.EventServerPacket import dev.adamko.kafkatorio.schema.packets.EventServerPacket
import dev.adamko.kafkatorio.webmap.state.FactorioGameState import dev.adamko.kafkatorio.webmap.layout.headerNav
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate import dev.adamko.kafkatorio.webmap.layout.homePage
import dev.adamko.kafkatorio.webmap.state.createFactorioReduxStore import dev.adamko.kafkatorio.webmap.layout.serverPage
import dev.adamko.kafkatorio.webmap.services.WebsocketService
import io.kvision.Application import io.kvision.Application
import io.kvision.BootstrapCssModule import io.kvision.BootstrapCssModule
import io.kvision.BootstrapIconsModule import io.kvision.BootstrapIconsModule
@ -12,17 +13,21 @@ import io.kvision.ChartModule
import io.kvision.CoreModule import io.kvision.CoreModule
import io.kvision.FontAwesomeModule import io.kvision.FontAwesomeModule
import io.kvision.html.div import io.kvision.html.div
import io.kvision.maps.Maps import io.kvision.html.header
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer import io.kvision.html.main
import io.kvision.module import io.kvision.module
import io.kvision.panel.root import io.kvision.panel.root
import io.kvision.redux.ReduxStore import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore
import io.kvision.require import io.kvision.require
import io.kvision.routing.Routing
import io.kvision.startApplication import io.kvision.startApplication
import kotlin.coroutines.CoroutineContext import io.kvision.state.bind
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
@ -33,54 +38,78 @@ import kotlinx.coroutines.flow.onEach
val rootJob = Job() val rootJob = Job()
class App( class App : Application() {
reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
wsService: WebsocketService,
) : Application(), CoroutineScope {
override val coroutineContext: CoroutineContext = CoroutineName("WebMapApp") private val coroutineScope: CoroutineScope = CoroutineScope(
Dispatchers.Default
+ SupervisorJob(rootJob)
+ CoroutineName("WebMapApp")
)
private var appState: MutableMap<String, Any> = mutableMapOf() private var appState: MutableMap<String, Any> = mutableMapOf()
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState var reduxStore: ReduxStore<SiteState, SiteAction> by appState
var wsService: WebsocketService by appState private set
// var wsService: WebsocketService by appState
// private set
init { init {
this.reduxStore = reduxStore this.reduxStore = createReduxStore(SiteState::plus, SiteState())
this.wsService = wsService // this.wsService = wsService
require("./css/kafkatorio.css") require("./css/kafkatorio.css")
} }
private val gameState: FactorioGameState // private val gameState: FactorioGameState
get() = reduxStore.getState() // get() = reduxStore.getState()
private val kvMaps: Maps
get() = gameState.map.kvMap
// private val kvMaps: Maps
// get() = gameState.map.kvMap
override fun start(state: Map<String, Any>) { override fun start(state: Map<String, Any>) {
Routing.init(useHash = false)
this.appState = state.toMutableMap() this.appState = state.toMutableMap()
val siteRouting = SiteRouting(reduxStore)
val websocketService = WebsocketService(reduxStore)
siteRouting.init()
root("kvapp") { root("kvapp") {
header().bind(siteRouting.siteStateStore) { state ->
headerNav(state)
}
div("Kafkatorio Web Map") div("Kafkatorio Web Map")
add(kvMaps)
main().bind(siteRouting.siteStateStore) { state ->
when (state.view) {
SiteView.HOME -> homePage(state)
SiteView.SERVER -> serverPage(state)
}
} }
kvMaps.leafletMap { // div("Kafkatorio Web Map")
invalidateSize(true) // add(kvMaps)
eachLayer({
if (it is GridLayer<*>) {
it.redraw()
}
})
} }
wsService.packetsFlow // kvMaps.leafletMap {
// invalidateSize(true)
// eachLayer({
// if (it is GridLayer<*>) {
// it.redraw()
// }
// })
// }
websocketService.packetsFlow
.filterIsInstance<EventServerPacket.ChunkTileSaved>() .filterIsInstance<EventServerPacket.ChunkTileSaved>()
.onEach { tileSavedEvent -> .onEach { tileSavedEvent ->
// println("[packetsFlow] triggering tile refresh ${tileSavedEvent.filename.value}") // println("[packetsFlow] triggering tile refresh ${tileSavedEvent.filename.value}")
gameState.map.refreshUpdatedTilePng(tileSavedEvent.filename) reduxStore.dispatch(SiteAction.EventServerUpdate(tileSavedEvent))
}.launchIn(this) // reduxStore.map.refreshUpdatedTilePng(tileSavedEvent.filename)
}.launchIn(coroutineScope)
} }
@ -96,14 +125,13 @@ class App(
fun main() { fun main() {
startApplication( startApplication(
{ {
val reduxStore = createFactorioReduxStore() // val reduxStore = createFactorioReduxStore()
val wsService = WebsocketService( // val wsService = WebsocketService(
// "ws://localhost:9073/ws", // TODO load from config // reduxStore = reduxStore,
reduxStore = reduxStore, // )
)
App( App(
reduxStore = reduxStore, // reduxStore = reduxStore,
wsService = wsService, // wsService = wsService,
) )
}, },
module.hot, module.hot,

View file

@ -1,4 +1,4 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap.config
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json

View file

@ -0,0 +1,16 @@
package dev.adamko.kafkatorio.webmap.layout
import dev.adamko.kafkatorio.webmap.SiteState
import dev.adamko.kafkatorio.webmap.SiteView
import io.kvision.core.Container
import io.kvision.html.div
import io.kvision.html.link
import io.kvision.html.nav
fun Container.headerNav(state: SiteState) {
nav(className = "navbar") {
div(className = "container") {
link("home", "#${SiteView.HOME.pathMatcher}")
}
}
}

View file

@ -0,0 +1,30 @@
package dev.adamko.kafkatorio.webmap.layout
import dev.adamko.kafkatorio.webmap.SiteState
import io.kvision.core.Container
import io.kvision.html.div
import io.kvision.html.h5
import io.kvision.html.link
fun Container.homePage(state: SiteState) {
div(className = "home-page") {
servers(state)
}
}
private fun Container.servers(state: SiteState) {
div(className = "servers") {
h5("Servers")
if (state.serverIds != null) {
state.serverIds.forEach { serverId ->
div {
link("server", "/servers/$serverId")
}
}
} else {
div("no servers available")
}
}
}

View file

@ -0,0 +1,26 @@
package dev.adamko.kafkatorio.webmap.layout
import dev.adamko.kafkatorio.webmap.SiteState
import io.kvision.core.Container
import io.kvision.html.div
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
fun Container.serverPage(state: SiteState) {
val factorioGameState = state.factorioGameState
div(className = "server-page") {
if (factorioGameState != null) {
add(factorioGameState.map.kvMap)
factorioGameState.map.kvMap.leafletMap {
invalidateSize(true)
eachLayer({
if (it is GridLayer<*>) {
it.redraw()
}
})
}
}
}
}

View file

@ -1,5 +1,6 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.schema.common.FactorioServerId
import dev.adamko.kafkatorio.schema.common.ServerMapTileLayer import dev.adamko.kafkatorio.schema.common.ServerMapTileLayer
import dev.adamko.kafkatorio.schema.common.ServerMapTilePngFilename import dev.adamko.kafkatorio.schema.common.ServerMapTilePngFilename
import dev.adamko.kafkatorio.webmap.externals.TileOnLoadFn import dev.adamko.kafkatorio.webmap.externals.TileOnLoadFn
@ -31,7 +32,9 @@ import org.w3c.fetch.RequestInit
import org.w3c.fetch.RequestMode import org.w3c.fetch.RequestMode
class FactorioMap { class FactorioMap(
private val serverId: FactorioServerId,
) {
val kvMap: Maps = Maps { val kvMap: Maps = Maps {
width = 800.px width = 800.px
@ -155,16 +158,18 @@ class FactorioMap {
return baseTileLayer return baseTileLayer
} }
private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String {
return "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
}
companion object { companion object {
const val DYNAMIC_RELOAD_ATT = "dynamic-reload" const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
private val serverName: String // private val serverName: String?
get() = window.location.pathname // get() = window.location.pathname
.substringAfter("kafkatorio/data/servers/") // .substringAfter("servers")
.substringBefore("/") // .substringBefore("/")
// .ifBlank { null }
private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String {
return "/kafkatorio/data/servers/$serverName/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
}
} }
} }

View file

@ -0,0 +1,161 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.schema.common.FactorioServerId
import dev.adamko.kafkatorio.schema.common.Tick
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.services.EventsServerClient
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
import io.kvision.navigo.Match
import io.kvision.navigo.Navigo
import io.kvision.redux.RAction
import io.kvision.redux.ReduxStore
import io.kvision.routing.routing
import kotlin.js.RegExp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class SiteRouting(
val siteStateStore: ReduxStore<SiteState, SiteAction>
) : CoroutineScope by CoroutineScope(Dispatchers.Default + SupervisorJob()) {
fun init() {
routing
.on(SiteView.HOME) { homepageView() }
.on(SiteView.SERVER) { match ->
serverView(match)
}
.resolve()
}
private fun homepageView() {
siteStateStore.dispatch(SiteAction.HomePage)
launch {
val serverIds = EventsServerClient.serverIds()
siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
}
}
private fun serverView(match: Match) {
val serverId = match.data[0] as? String ?: error("invalid server id")
siteStateStore.dispatch(SiteAction.ServerPage(FactorioServerId(serverId)))
}
}
data class SiteState(
val view: SiteView = SiteView.HOME,
val serverIds: List<FactorioServerId>? = null,
val factorioGameState: FactorioGameState? = null,
) {
operator fun plus(action: SiteAction): SiteState = when (action) {
is SiteAction.HomePage -> copy(view = SiteView.HOME)
is SiteAction.ServerPage -> copy(
view = SiteView.SERVER,
factorioGameState = FactorioGameState(action.serverId),
)
is SiteAction.ServerIdsLoaded -> copy(serverIds = action.serverIds)
is SiteAction.FactorioUpdate -> copy(
factorioGameState = factorioGameState?.let { it + action }
)
is SiteAction.EventServerUpdate -> copy(
)
}
}
sealed interface SiteAction : RAction {
object HomePage : SiteAction
data class ServerPage(val serverId: FactorioServerId) : SiteAction
data class ServerIdsLoaded(val serverIds: List<FactorioServerId>) : SiteAction
/** Updates from `events-server` */
sealed class FactorioUpdate : SiteAction {
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
}
data class EventServerUpdate(val packet: EventServerPacket) : SiteAction
}
sealed interface SiteView {
val pathMatcher: Any
sealed interface StaticPath : SiteView {
override val pathMatcher: String
}
sealed interface DynamicPath : SiteView {
override val pathMatcher: RegExp
}
object HOME : StaticPath {
override val pathMatcher: String = "/"
}
object SERVER : DynamicPath {
override val pathMatcher: RegExp = RegExp("^servers/${FactorioServerId.validIdRegex}")
}
}
//val SiteView.urlMatcher: String
// get() = when (this) {
// SiteView.HOME -> "/"
// SiteView.SERVER -> "servers/${FactorioServerId.validIdRegex}"
// }
private fun Navigo.on(view: SiteView, handler: (Match) -> Unit): Navigo {
return when (view) {
is SiteView.StaticPath -> on(view.pathMatcher, { match: Match -> handler(match) })
is SiteView.DynamicPath -> on(view.pathMatcher, { match: Match -> handler(match) })
}
}
//private fun Navigo.on(
// view: SiteView,
// before: BeforeHook? = null,
// after: AfterHook? = null,
// leave: LeaveHook? = null,
// already: AlreadyHook? = null,
// handler: (Match) -> Unit,
//): Navigo {
//
// val hooks: RouteHooks? =
// if (before != null || after != null || leave != null || already != null) {
// jso {
// if (before != null) this@jso.before = before
// if (after != null) this@jso.after = after
// if (leave != null) this@jso.leave = leave
// if (already != null) this@jso.already = already
// }
// } else {
// null
// }
//
// return when (view) {
// is SiteView.StaticPath -> when (hooks) {
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
// }
//
// is SiteView.DynamicPath -> when (hooks) {
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
// }
// }
//}

View file

@ -0,0 +1,25 @@
package dev.adamko.kafkatorio.webmap.services
import dev.adamko.kafkatorio.schema.common.FactorioServerId
import io.kvision.rest.RestClient
import io.kvision.rest.call
import kotlinx.browser.window
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.await
object EventsServerClient :
CoroutineScope by CoroutineScope(Dispatchers.Default + SupervisorJob()) {
private val restClient = RestClient()
/** Fetch all possible server IDs. */
suspend fun serverIds(): List<FactorioServerId> =
restClient
.call<List<FactorioServerId>>(window.location.origin + "/kafkatorio/data/servers")
.await()
}

View file

@ -1,4 +1,4 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap.services
import dev.adamko.kafkatorio.schema.packets.ConfigurationUpdate import dev.adamko.kafkatorio.schema.packets.ConfigurationUpdate
import dev.adamko.kafkatorio.schema.packets.ConsoleChatUpdate import dev.adamko.kafkatorio.schema.packets.ConsoleChatUpdate
@ -12,8 +12,11 @@ import dev.adamko.kafkatorio.schema.packets.MapChunkTileUpdate
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
import dev.adamko.kafkatorio.webmap.state.FactorioGameState import dev.adamko.kafkatorio.webmap.SiteAction
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate import dev.adamko.kafkatorio.webmap.SiteState
import dev.adamko.kafkatorio.webmap.config.ApplicationProperties
import dev.adamko.kafkatorio.webmap.config.jsonMapper
import dev.adamko.kafkatorio.webmap.rootJob
import io.kvision.redux.ReduxStore import io.kvision.redux.ReduxStore
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
@ -29,14 +32,13 @@ import org.w3c.dom.events.Event
class WebsocketService( class WebsocketService(
wsUrl: String = Props.websocketServerUrl, private val siteStateStore: ReduxStore<SiteState, SiteAction>
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
) : CoroutineScope { ) : CoroutineScope {
override val coroutineContext: CoroutineContext = override val coroutineContext: CoroutineContext =
CoroutineName("WebsocketService") + Job(rootJob) CoroutineName("WebsocketService") + Job(rootJob)
private val ws = WebSocket(wsUrl) private val ws = WebSocket(ApplicationProperties.websocketServerUrl)
// private val ws = WebSocket("ws://localhost:12080/ws/foo") // private val ws = WebSocket("ws://localhost:12080/ws/foo")
private val _packetsFlow = MutableSharedFlow<EventServerPacket>() private val _packetsFlow = MutableSharedFlow<EventServerPacket>()
@ -78,7 +80,9 @@ class WebsocketService(
is EventServerPacket.Kafkatorio -> is EventServerPacket.Kafkatorio ->
when (val packetData = packet.packet.data) { when (val packetData = packet.packet.data) {
is PlayerUpdate -> is PlayerUpdate ->
reduxStore.dispatch(FactorioUpdate.Player(packet.packet.tick, packetData)) siteStateStore.dispatch(
SiteAction.FactorioUpdate.Player(packet.packet.tick, packetData)
)
is ConfigurationUpdate, is ConfigurationUpdate,
is ConsoleChatUpdate, is ConsoleChatUpdate,

View file

@ -1,4 +1,4 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap.services
import dev.adamko.kafkatorio.schema.common.MapEntityPosition import dev.adamko.kafkatorio.schema.common.MapEntityPosition
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate import dev.adamko.kafkatorio.schema.packets.PlayerUpdate

View file

@ -0,0 +1,82 @@
package dev.adamko.kafkatorio.webmap.state
import dev.adamko.kafkatorio.schema.common.Colour
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
import dev.adamko.kafkatorio.schema.common.toHexString
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.services.latLng
import io.kvision.maps.externals.leaflet.geo.LatLng
sealed interface PlayerProperties {
val position: MapEntityPosition?
val surfaceIndex: SurfaceIndex?
val colour: Colour?
val name: String?
val latLng: LatLng?
val isConnected: Boolean
val isShowOnMap: Boolean
data class Partial(
override val position: MapEntityPosition? = null,
override val surfaceIndex: SurfaceIndex? = null,
override val colour: Colour? = null,
override val name: String? = null,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng? = null
}
data class Complete(
override val position: MapEntityPosition,
override val surfaceIndex: SurfaceIndex,
override val colour: Colour,
override val name: String,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng = position.latLng()
val hexColour: String = colour.toHexString(false)
}
fun update(update: PlayerUpdate): PlayerProperties {
val position = update.position ?: this.position
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
val colour = update.colour ?: this.colour
val name = update.name ?: this.name
val isConnected = update.isConnected
val isShowOnMap = update.isShowOnMap
return when {
position != null
&& surfaceIndex != null
&& colour != null
&& name != null ->
Complete(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
else ->
Partial(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
}
}
}

View file

@ -1,22 +1,17 @@
package dev.adamko.kafkatorio.webmap.state package dev.adamko.kafkatorio.webmap.state
import dev.adamko.kafkatorio.schema.common.Colour
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
import dev.adamko.kafkatorio.schema.common.PlayerIndex import dev.adamko.kafkatorio.schema.common.PlayerIndex
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
import dev.adamko.kafkatorio.schema.common.Tick import dev.adamko.kafkatorio.schema.common.Tick
import dev.adamko.kafkatorio.schema.common.toHexString
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.latLng import dev.adamko.kafkatorio.webmap.SiteAction
import io.kvision.maps.Maps import io.kvision.maps.Maps
import io.kvision.maps.externals.leaflet.geo.LatLng
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
import kotlinx.js.jso import kotlinx.js.jso
fun handlePlayerUpdate( fun handlePlayerUpdate(
gameState: FactorioGameState, gameState: FactorioGameState,
update: FactorioUpdate.Player, update: SiteAction.FactorioUpdate.Player,
): FactorioGameState { ): FactorioGameState {
val previous = gameState.players[update.data.key.index] val previous = gameState.players[update.data.key.index]
@ -105,76 +100,3 @@ data class PlayerState(
} }
} }
} }
sealed interface PlayerProperties {
val position: MapEntityPosition?
val surfaceIndex: SurfaceIndex?
val colour: Colour?
val name: String?
val latLng: LatLng?
val isConnected: Boolean
val isShowOnMap: Boolean
data class Partial(
override val position: MapEntityPosition? = null,
override val surfaceIndex: SurfaceIndex? = null,
override val colour: Colour? = null,
override val name: String? = null,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng? = null
}
data class Complete(
override val position: MapEntityPosition,
override val surfaceIndex: SurfaceIndex,
override val colour: Colour,
override val name: String,
override val isConnected: Boolean = false,
override val isShowOnMap: Boolean = false,
) : PlayerProperties {
override val latLng: LatLng = position.latLng()
val hexColour: String = colour.toHexString(false)
}
fun update(update: PlayerUpdate): PlayerProperties {
val position = update.position ?: this.position
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
val colour = update.colour ?: this.colour
val name = update.name ?: this.name
val isConnected = update.isConnected
val isShowOnMap = update.isShowOnMap
return when {
position != null
&& surfaceIndex != null
&& colour != null
&& name != null ->
Complete(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
else ->
Partial(
position = position,
surfaceIndex = surfaceIndex,
colour = colour,
name = name,
isConnected = isConnected,
isShowOnMap = isShowOnMap,
)
}
}
}

View file

@ -1,41 +1,33 @@
package dev.adamko.kafkatorio.webmap.state package dev.adamko.kafkatorio.webmap.state
import dev.adamko.kafkatorio.schema.common.FactorioServerId
import dev.adamko.kafkatorio.schema.common.PlayerIndex import dev.adamko.kafkatorio.schema.common.PlayerIndex
import dev.adamko.kafkatorio.schema.common.Tick
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.webmap.FactorioMap import dev.adamko.kafkatorio.webmap.FactorioMap
import dev.adamko.kafkatorio.webmap.SiteAction
import io.kvision.maps.externals.leaflet.layer.LayerGroup import io.kvision.maps.externals.leaflet.layer.LayerGroup
import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore
import redux.RAction
fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> = //fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
createReduxStore( // createReduxStore(
::factorioGameReducer, // ::factorioGameReducer,
FactorioGameState( // FactorioGameState(
map = FactorioMap(), // map = FactorioMap(),
players = emptyMap(), // players = emptyMap(),
) // )
) // )
data class FactorioGameState( data class FactorioGameState(
val map: FactorioMap, val serverId: FactorioServerId,
val players: Map<PlayerIndex, PlayerState>, val map: FactorioMap = FactorioMap(serverId),
val players: Map<PlayerIndex, PlayerState> = emptyMap(),
) { ) {
val playerIconsLayer: LayerGroup val playerIconsLayer: LayerGroup
get() = map.playerIconsLayer get() = map.playerIconsLayer
}
operator fun plus(action: SiteAction.FactorioUpdate): FactorioGameState =
sealed class FactorioUpdate : RAction { when (action) {
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate() is SiteAction.FactorioUpdate.Player -> handlePlayerUpdate(this, action)
}
fun factorioGameReducer(gameState: FactorioGameState, update: FactorioUpdate): FactorioGameState =
when (update) {
is FactorioUpdate.Player -> handlePlayerUpdate(gameState, update)
} }
}